From a385e15f64e6947ba5a71bd5beebb80a6e223083 Mon Sep 17 00:00:00 2001 From: Arnaud Jamin Date: Tue, 28 Nov 2023 01:28:09 -0500 Subject: [PATCH] CogImGui: Add imgui viewport support --- Content/Core/Hud/WBP_Menu.uasset | Bin 154786 -> 155708 bytes .../Private/CogEngineWindow_Collisions.cpp | 2 +- .../Private/CogEngineWindow_Selection.cpp | 35 +- .../Private/CogEngineWindow_Slate.cpp | 105 +++ .../CogEngine/Public/CogEngineWindow_Slate.h | 22 + .../Private/CogImGuiInputProcessor.cpp | 28 +- .../CogImgui/Private/CogImguiContext.cpp | 726 ++++++++++++++++++ .../CogImgui/Private/CogImguiDrawList.cpp | 76 +- .../CogImgui/Private/CogImguiHelper.cpp | 11 +- .../CogImgui/Private/CogImguiModule.cpp | 67 +- .../Private/CogImguiTextureManager.cpp | 195 ----- .../CogImgui/Private/CogImguiWidget.cpp | 428 ++--------- .../CogImgui/Public/CogImGuiInputProcessor.h | 7 +- .../Source/CogImgui/Public/CogImguiContext.h | 117 +++ .../Source/CogImgui/Public/CogImguiDrawList.h | 69 +- .../Source/CogImgui/Public/CogImguiHelper.h | 6 +- .../Source/CogImgui/Public/CogImguiModule.h | 20 - .../CogImgui/Public/CogImguiTextureManager.h | 147 ---- .../Source/CogImgui/Public/CogImguiWidget.h | 83 +- .../Source/CogWindow/Private/CogWindow.cpp | 3 +- .../CogWindow/Private/CogWindowManager.cpp | 74 +- .../CogWindow/Private/CogWindow_Inputs.cpp | 87 --- .../CogWindow/Private/CogWindow_Settings.cpp | 132 +++- .../CogWindow/Public/CogWindowManager.h | 63 +- .../CogWindow/Public/CogWindow_Inputs.h | 59 -- .../CogWindow/Public/CogWindow_Settings.h | 81 ++ Source/CogSample/CogSampleGameState.cpp | 3 + 27 files changed, 1386 insertions(+), 1260 deletions(-) create mode 100644 Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp create mode 100644 Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Slate.h create mode 100644 Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp delete mode 100644 Plugins/Cog/Source/CogImgui/Private/CogImguiTextureManager.cpp create mode 100644 Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h delete mode 100644 Plugins/Cog/Source/CogImgui/Public/CogImguiTextureManager.h diff --git a/Content/Core/Hud/WBP_Menu.uasset b/Content/Core/Hud/WBP_Menu.uasset index 15529b2f62f8a4c82a9f27e8ad6ca5f7cbb9db7c..a9013810b7a8fdc6acd9c4876e4d0f7eda537278 100644 GIT binary patch literal 155708 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 literal 154786 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 QueryRadius = Config->QueryThickness; break; diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp index f58e7f0..40781fc 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp @@ -4,6 +4,7 @@ #include "CogDebugSettings.h" #include "CogEngineReplicator.h" #include "CogImguiModule.h" +#include "CogImguiHelper.h" #include "CogImguiInputHelper.h" #include "CogWindowManager.h" #include "CogWindowWidgets.h" @@ -136,8 +137,8 @@ void FCogEngineWindow_Selection::ToggleSelectionMode() void FCogEngineWindow_Selection::ActivateSelectionMode() { bSelectionModeActive = true; - bIsInputEnabledBeforeEnteringSelectionMode = GetOwner()->GetImGuiWidget()->GetEnableInput(); - GetOwner()->GetImGuiWidget()->SetEnableInput(true); + bIsInputEnabledBeforeEnteringSelectionMode = GetOwner()->GetContext().GetEnableInput(); + GetOwner()->GetContext().SetEnableInput(true); GetOwner()->SetHideAllWindows(true); } @@ -158,7 +159,7 @@ void FCogEngineWindow_Selection::DeactivateSelectionMode() // When in selection mode we need imgui to have the input focus // When leaving selection mode we want to leave it as is was before //-------------------------------------------------------------------------------------------- - GetOwner()->GetImGuiWidget()->SetEnableInput(bIsInputEnabledBeforeEnteringSelectionMode); + GetOwner()->GetContext().SetEnableInput(bIsInputEnabledBeforeEnteringSelectionMode); GetOwner()->SetHideAllWindows(false); } @@ -393,15 +394,23 @@ void FCogEngineWindow_Selection::TickSelectionMode() return; } - ImDrawList* DrawList = ImGui::GetBackgroundDrawList(); - DrawList->AddRect(ImVec2(0, 0), ImGui::GetIO().DisplaySize, IM_COL32(255, 0, 0, 128), 0.0f, 0, 20.0f); - FCogWindowWidgets::AddTextWithShadow(DrawList, ImVec2(20, 20), IM_COL32(255, 255, 255, 255), "Picking Mode. \n[LMB] Pick \n[RMB] Cancel"); + ImGuiViewport* Viewport = ImGui::GetMainViewport(); + if (Viewport == nullptr) + { + return; + } + + 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"); TSubclassOf SelectedActorClass = GetSelectedActorClass(); AActor* HoveredActor = nullptr; FVector WorldOrigin, WorldDirection; - if (UGameplayStatics::DeprojectScreenToWorld(PlayerController, FCogImguiHelper::ToVector2D(ImGui::GetMousePos()), WorldOrigin, WorldDirection)) + if (UGameplayStatics::DeprojectScreenToWorld(PlayerController, FCogImguiHelper::ToFVector2D(ImGui::GetMousePos() - ViewportPos), WorldOrigin, WorldDirection)) { //-------------------------------------------------------------------------------------------------------- // Prioritize another actor than the selected actor unless we only touch the selected actor. @@ -463,7 +472,13 @@ void FCogEngineWindow_Selection::DrawActorFrame(const AActor& Actor) return; } - ImDrawList* DrawList = ImGui::GetBackgroundDrawList(); + ImGuiViewport* Viewport = ImGui::GetMainViewport(); + if (Viewport == nullptr) + { + return; + } + + ImDrawList* DrawList = ImGui::GetBackgroundDrawList(Viewport); FVector BoxOrigin, BoxExtent; @@ -502,8 +517,8 @@ void FCogEngineWindow_Selection::DrawActorFrame(const AActor& Actor) if (ComputeBoundingBoxScreenPosition(PlayerController, BoxOrigin, BoxExtent, ScreenPosMin, ScreenPosMax)) { const ImU32 Color = (&Actor == GetSelection()) ? IM_COL32(255, 255, 255, 255) : IM_COL32(255, 255, 255, 128); - DrawList->AddRect(FCogImguiHelper::ToImVec2(ScreenPosMin), FCogImguiHelper::ToImVec2(ScreenPosMax), Color, 0.0f, 0, 1.0f); - FCogWindowWidgets::AddTextWithShadow(DrawList, FCogImguiHelper::ToImVec2(ScreenPosMin + FVector2D(0, -14.0f)), Color, TCHAR_TO_ANSI(*GetActorName(Actor))); + DrawList->AddRect(FCogImguiHelper::ToImVec2(ScreenPosMin) + Viewport->Pos, FCogImguiHelper::ToImVec2(ScreenPosMax) + Viewport->Pos, Color, 0.0f, 0, 1.0f); + FCogWindowWidgets::AddTextWithShadow(DrawList, FCogImguiHelper::ToImVec2(ScreenPosMin + FVector2D(0, -14.0f)) + Viewport->Pos, Color, TCHAR_TO_ANSI(*GetActorName(Actor))); } } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp new file mode 100644 index 0000000..1d5837a --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp @@ -0,0 +1,105 @@ +#include "CogEngineWindow_Slate.h" + +#include "CogImguiHelper.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Application/SlateUser.h" + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Slate::RenderHelp() +{ + ImGui::Text( + "This window displays slate debug info. " + ); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Slate::RenderContent() +{ + Super::RenderContent(); + + + if (FSlateApplication::IsInitialized() == false) + { + ImGui::Text("Not initialized"); + return; + } + + FSlateApplication& SlateApp = FSlateApplication::Get(); + + static int32 SelectedUserIndex = 0; + + ImGui::SetNextItemWidth(-1); + if (ImGui::BeginCombo("##User", TCHAR_TO_ANSI(*FString::Printf(TEXT("%d"), SelectedUserIndex)))) + { + SlateApp.ForEachUser([this](FSlateUser& User) + { + if (ImGui::Selectable(TCHAR_TO_ANSI(*FString::Printf(TEXT("%d"), SelectedUserIndex)), false)) + { + SelectedUserIndex = User.GetUserIndex(); + } + }); + ImGui::EndCombo(); + } + + if (TSharedPtr User = SlateApp.GetUser(SelectedUserIndex)) + { + RenderUser(*User.Get()); + } + +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Slate::RenderUser(FSlateUser& User) +{ + + if (ImGui::BeginTable("SlateUser", 2, ImGuiTableFlags_Borders)) + { + const ImVec4 LabelColor(1.0f, 1.0f, 1.0f, 0.5f); + + ImGui::TableSetupColumn("Property"); + ImGui::TableSetupColumn("Value"); + + + //------------------------ + // Focused Widget + //------------------------ + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(LabelColor, "Focused Widget"); + ImGui::TableNextColumn(); + FString FocusedWidgetText = "None"; + if (TSharedPtr FocusedWidget = User.GetFocusedWidget()) + { + FocusedWidgetText = FocusedWidget->ToString(); + } + ImGui::Text("%s", TCHAR_TO_ANSI(*FocusedWidgetText)); + + //------------------------ + // Cursor Captor + //------------------------ + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(LabelColor, "Cursor Captor"); + ImGui::TableNextColumn(); + FString CursorCaptoreWidgetText = "None"; + if (TSharedPtr CursorCaptoreWidget = User.GetCursorCaptor()) + { + CursorCaptoreWidgetText = CursorCaptoreWidget->ToString(); + } + ImGui::Text("%s", TCHAR_TO_ANSI(*CursorCaptoreWidgetText)); + + //------------------------ + // Cursor Position + //------------------------ + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(LabelColor, "Cursor Position"); + ImGui::TableNextColumn(); + ImVec2 CursorPosition = FCogImguiHelper::ToImVec2(User.GetCursorPosition()); + ImGui::InputFloat2("##Cursor Position", &CursorPosition.x, "%0.0f"); + + + + ImGui::EndTable(); + } +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Slate.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Slate.h new file mode 100644 index 0000000..2780ea0 --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Slate.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogWindow.h" + +class FSlateUser; + +class COGENGINE_API FCogEngineWindow_Slate : public FCogWindow +{ + typedef FCogWindow Super; + +public: + +protected: + + virtual void RenderHelp() override; + + virtual void RenderContent() override; + + virtual void RenderUser(FSlateUser& User); + +}; diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImGuiInputProcessor.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImGuiInputProcessor.cpp index d7ee6ce..fb04382 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImGuiInputProcessor.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImGuiInputProcessor.cpp @@ -3,9 +3,11 @@ #include "CogImguiHelper.h" #include "CogImguiInputHelper.h" #include "CogImguiWidget.h" +#include "CogImguiContext.h" #include "GameFramework/InputSettings.h" #include "GameFramework/PlayerInput.h" #include "imgui.h" +#include "imgui_internal.h" #include "Slate/SGameLayerManager.h" #if WITH_EDITOR @@ -16,9 +18,10 @@ constexpr bool ForwardEvent = false; constexpr bool TerminateEvent = true; //-------------------------------------------------------------------------------------------------------------------------- -FImGuiInputProcessor::FImGuiInputProcessor(UPlayerInput* InPlayerInput, SCogImguiWidget* InMainWidget) +FImGuiInputProcessor::FImGuiInputProcessor(UPlayerInput* InPlayerInput, FCogImguiContext* InContext, SCogImguiWidget* InMainWidget) { PlayerInput = InPlayerInput; + Context = InContext; MainWidget = InMainWidget; } @@ -43,14 +46,16 @@ void FImGuiInputProcessor::Tick(const float DeltaTime, FSlateApplication& SlateA AddMousePosEvent(SlateApp.GetCursorPos()); - if ((IO.ConfigFlags & ImGuiConfigFlags_NoMouse) == 0) + const bool bHasMouse = (IO.ConfigFlags & ImGuiConfigFlags_NoMouse) == 0; + const bool bUpdateMouseMouseCursor = (IO.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) == 0; + if (bHasMouse && bUpdateMouseMouseCursor) { SlateCursor->SetType(FCogImguiInputHelper::ToSlateMouseCursor(ImGui::GetMouseCursor())); } if (IO.WantSetMousePos) { - SlateApp.SetCursorPos(FCogImguiHelper::ToVector2D(IO.MousePos)); + SlateApp.SetCursorPos(FCogImguiHelper::ToFVector2D(IO.MousePos)); //UE_LOG(LogCogImGui, VeryVerbose, TEXT("FImGuiInputProcessor::Tick | SetCursorPos")); } } @@ -175,7 +180,7 @@ bool FImGuiInputProcessor::HandleMouseMoveEvent(FSlateApplication& SlateApp, con { AddMousePosEvent(Event.GetScreenSpacePosition()); - if (MainWidget->GetEnableInput() && MainWidget->GetShareMouse() == false) + if (Context->GetEnableInput() && Context->GetShareMouse() == false && IsMouseInsideMainViewport()) { return TerminateEvent; } @@ -211,7 +216,7 @@ bool FImGuiInputProcessor::HandleMouseButtonEvent(FSlateApplication& SlateApp, c const uint32 Button = ToImGuiMouseButton(Event.GetEffectingButton()); IO.AddMouseButtonEvent(Button, IsButtonDown); - if (MainWidget->GetEnableInput() && MainWidget->GetShareMouse() == false) + if (Context->GetEnableInput() && Context->GetShareMouse() == false && IsMouseInsideMainViewport()) { const bool Result = TerminateEvent; UE_LOG(LogCogImGui, VeryVerbose, TEXT("FImGuiInputProcessor::HandleMouseButtonEvent | Button:%d | IsButtonDown:%d | WantCaptureMouse:%d | TerminateEvent:%d | ShareMouse == false"), Button, IsButtonDown, IO.WantCaptureMouse, Result); @@ -237,6 +242,19 @@ void FImGuiInputProcessor::AddMousePosEvent(const FVector2D& MousePosition) cons } } +//-------------------------------------------------------------------------------------------------------------------------- +bool FImGuiInputProcessor::IsMouseInsideMainViewport() +{ + if (ImGuiViewportP* Viewport = (ImGuiViewportP*)ImGui::GetMainViewport()) + { + ImGuiIO& IO = ImGui::GetIO(); + const bool Result = Viewport->GetMainRect().Contains(IO.MousePos); + return Result; + } + + return false; +} + //-------------------------------------------------------------------------------------------------------------------------- bool FImGuiInputProcessor::HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& Event, const FPointerEvent* GestureEvent) { diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp new file mode 100644 index 0000000..5acbee3 --- /dev/null +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp @@ -0,0 +1,726 @@ +#include "CogImGuiContext.h" + +#include "CogImguiHelper.h" +#include "CogImguiInputHelper.h" +#include "CogImguiInputProcessor.h" +#include "CogImguiWidget.h" +#include "Engine/Console.h" +#include "Engine/LocalPlayer.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Application/SlateUser.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerInput.h" +#include "imgui.h" +#include "imgui_internal.h" +#include "implot.h" +#include "TextureResource.h" +#include "Widgets/SViewport.h" +#include "Widgets/SWindow.h" + +static UPlayerInput* GetPlayerInput(const UWorld* World); + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::Initialize() +{ + IMGUI_CHECKVERSION(); + + if (FSlateApplication::IsInitialized() == false) + { + return; + } + FSlateApplication& SlateApp = FSlateApplication::Get(); + + GameViewport = GEngine->GameViewport; + + SAssignNew(MainWidget, SCogImguiWidget) + .Context(this); + + GameViewport->AddViewportWidgetContent(MainWidget.ToSharedRef(), TNumericLimits::Max()); + + //-------------------------------------------------------------------- + // Register input processor to forward input events to imgui + //-------------------------------------------------------------------- + if (FSlateApplication::IsInitialized()) + { + UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GameViewport->GetWorld()); + InputProcessor = MakeShared(PlayerInput, this, MainWidget.Get()); + FSlateApplication::Get().RegisterInputPreProcessor(InputProcessor.ToSharedRef(), 0); + } + + ImGuiContext = ImGui::CreateContext(); + PlotContext = ImPlot::CreateContext(); + ImPlot::SetImGuiContext(ImGuiContext); + + ImGuiIO& IO = ImGui::GetIO(); + IO.UserData = this; + + IO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + IO.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + IO.ConfigFlags |= ImGuiConfigFlags_NavNoCaptureKeyboard; + IO.ConfigFlags |= ImGuiConfigFlags_NavEnableSetMousePos; + IO.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + IO.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + IO.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; + IO.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; + IO.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; + IO.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; + IO.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + + //-------------------------------------------------------------------- + // + //-------------------------------------------------------------------- + ImGuiViewport* MainViewport = ImGui::GetMainViewport(); + FImGuiViewportData* ViewportData = new FImGuiViewportData(); + MainViewport->PlatformUserData = ViewportData; + ViewportData->Window = SlateApp.GetActiveTopLevelWindow(); + ViewportData->Context = this; + ViewportData->Widget = MainWidget; + + const char* InitFilenameTemp = TCHAR_TO_ANSI(*FCogImguiHelper::GetIniFilePath("imgui")); + ImStrncpy(IniFilename, InitFilenameTemp, IM_ARRAYSIZE(IniFilename)); + IO.IniFilename = IniFilename; + + ImGuiPlatformIO& PlatformIO = ImGui::GetPlatformIO(); + PlatformIO.Platform_CreateWindow = ImGui_CreateWindow; + PlatformIO.Platform_DestroyWindow = ImGui_DestroyWindow; + PlatformIO.Platform_ShowWindow = ImGui_ShowWindow; + PlatformIO.Platform_SetWindowPos = ImGui_SetWindowPos; + PlatformIO.Platform_GetWindowPos = ImGui_GetWindowPos; + PlatformIO.Platform_SetWindowSize = ImGui_SetWindowSize; + PlatformIO.Platform_GetWindowSize = ImGui_GetWindowSize; + PlatformIO.Platform_SetWindowFocus = ImGui_SetWindowFocus; + PlatformIO.Platform_GetWindowFocus = ImGui_GetWindowFocus; + PlatformIO.Platform_GetWindowMinimized = ImGui_GetWindowMinimized; + PlatformIO.Platform_SetWindowTitle = ImGui_SetWindowTitle; + PlatformIO.Platform_SetWindowAlpha = ImGui_SetWindowAlpha; + PlatformIO.Platform_RenderWindow = ImGui_RenderWindow; + + if (const TSharedPtr PlatformApplication = SlateApp.GetPlatformApplication()) + { + FDisplayMetrics DisplayMetrics; + PlatformApplication->GetInitialDisplayMetrics(DisplayMetrics); + PlatformApplication->OnDisplayMetricsChanged().AddRaw(this, &FCogImguiContext::OnDisplayMetricsChanged); + OnDisplayMetricsChanged(DisplayMetrics); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::Shutdown() +{ + ImGuiViewport* MainViewport = ImGui::GetMainViewport(); + if (const FImGuiViewportData* ViewportData = static_cast(MainViewport->PlatformUserData)) + { + delete ViewportData; + MainViewport->PlatformUserData = nullptr; + } + + if (FSlateApplication::IsInitialized()) + { + FSlateApplication& SlateApp = FSlateApplication::Get(); + + if (InputProcessor.IsValid()) + { + SlateApp.UnregisterInputPreProcessor(InputProcessor); + } + + if (const TSharedPtr PlatformApplication = SlateApp.GetPlatformApplication()) + { + PlatformApplication->OnDisplayMetricsChanged().RemoveAll(this); + } + } + + if (PlotContext) + { + ImPlot::DestroyContext(PlotContext); + PlotContext = nullptr; + } + + if (ImGuiContext) + { + ImGui::DestroyContext(ImGuiContext); + ImGuiContext = nullptr; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::OnDisplayMetricsChanged(const FDisplayMetrics& DisplayMetrics) const +{ + ImGuiPlatformIO& PlatformIO = ImGui::GetPlatformIO(); + PlatformIO.Monitors.resize(0); + + for (const FMonitorInfo& Monitor : DisplayMetrics.MonitorInfo) + { + ImGuiPlatformMonitor ImGuiMonitor; + ImGuiMonitor.MainPos = ImVec2(Monitor.DisplayRect.Left, Monitor.DisplayRect.Top); + ImGuiMonitor.MainSize = ImVec2(Monitor.DisplayRect.Right - Monitor.DisplayRect.Left, Monitor.DisplayRect.Bottom - Monitor.DisplayRect.Top); + ImGuiMonitor.WorkPos = ImVec2(Monitor.WorkArea.Left, Monitor.WorkArea.Top); + ImGuiMonitor.WorkSize = ImVec2(Monitor.WorkArea.Right - Monitor.WorkArea.Left, Monitor.WorkArea.Bottom - Monitor.WorkArea.Top); + ImGuiMonitor.DpiScale = Monitor.DPI; + + if (Monitor.bIsPrimary) + { + PlatformIO.Monitors.push_front(ImGuiMonitor); + } + else + { + PlatformIO.Monitors.push_back(ImGuiMonitor); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::BeginFrame(float InDeltaTime) +{ + ImGui::SetCurrentContext(ImGuiContext); + ImPlot::SetImGuiContext(ImGuiContext); + ImPlot::SetCurrentContext(PlotContext); + + ImGuiIO& IO = ImGui::GetIO(); + IO.DeltaTime = InDeltaTime; + IO.DisplaySize = FCogImguiHelper::ToImVec2(MainWidget->GetTickSpaceGeometry().GetAbsoluteSize()); + + //------------------------------------------------------------------------------------------------------- + // Build font + //------------------------------------------------------------------------------------------------------- + if (IO.Fonts->IsBuilt() == false || FontAtlasTexturePtr.IsValid() == false) + { + BuildFont(); + } + + //------------------------------------------------------------------------------------------------------- + // Update which viewport is under the mouse + //------------------------------------------------------------------------------------------------------- + ImGuiID MouseViewportId = 0; + + FSlateApplication& SlateApp = FSlateApplication::Get(); + FWidgetPath WidgetsUnderCursor = SlateApp.LocateWindowUnderMouse(SlateApp.GetCursorPos(), SlateApp.GetInteractiveTopLevelWindows()); + if (WidgetsUnderCursor.IsValid()) + { + TSharedRef Window = WidgetsUnderCursor.GetWindow(); + ImGuiID* ViewportId = WindowToViewportMap.Find(Window); + + if (ViewportId != nullptr) + { + MouseViewportId = *ViewportId; + } + else + { + MouseViewportId = ImGui::GetMainViewport()->ID; + } + } + + IO.AddMouseViewportEvent(MouseViewportId); + + //------------------------------------------------------------------------------------------------------- + // 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) + { + IO.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; + + TryReleaseGameMouseCapture(); + } + else + { + IO.ConfigFlags |= ImGuiConfigFlags_NoMouse; + } + + //------------------------------------------------------------------------------------------------------- + // + //------------------------------------------------------------------------------------------------------- + const bool bHasMouse = (IO.ConfigFlags & ImGuiConfigFlags_NoMouse) == 0; + const bool bUpdateMouseMouseCursor = (IO.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) == 0; + if (bHasMouse && bUpdateMouseMouseCursor) + { + MainWidget->SetCursor(FCogImguiInputHelper::ToSlateMouseCursor(ImGui::GetMouseCursor())); + } + + //------------------------------------------------------------------------------------------------------- + // + //------------------------------------------------------------------------------------------------------- + if (bRefreshDPIScale) + { + RefreshDPIScale(); + } + + ImGui::NewFrame(); + + DrawDebug(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::EndFrame() +{ + ImGui::Render(); + ImGui_RenderWindow(ImGui::GetMainViewport(), nullptr); + + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_CreateWindow(ImGuiViewport* Viewport) +{ + if (Viewport->ParentViewportId == 0) + { + return; + } + + ImGuiViewport* ParentViewport = ImGui::FindViewportByID(Viewport->ParentViewportId); + if (ParentViewport == nullptr) + { + return; + } + + const FImGuiViewportData* ParentViewportData = static_cast(ParentViewport->PlatformUserData); + if (ParentViewportData == nullptr) + { + return; + } + + FCogImguiContext* Context = ParentViewportData->Context.Get(); + + const bool bTooltipWindow = (Viewport->Flags & ImGuiViewportFlags_TopMost); + const bool bPopupWindow = (Viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon); + + static FWindowStyle WindowStyle = FWindowStyle() + .SetActiveTitleBrush(FSlateNoResource()) + .SetInactiveTitleBrush(FSlateNoResource()) + .SetFlashTitleBrush(FSlateNoResource()) + .SetOutlineBrush(FSlateNoResource()) + .SetBorderBrush(FSlateNoResource()) + .SetBackgroundBrush(FSlateNoResource()) + .SetChildBackgroundBrush(FSlateNoResource()); + + TSharedPtr Widget; + + const TSharedRef Window = + SNew(SWindow) + .Type(bTooltipWindow ? EWindowType::ToolTip : EWindowType::Normal) + .Style(&WindowStyle) + .ScreenPosition(FCogImguiHelper::ToFVector2D(Viewport->Pos)) + .ClientSize(FCogImguiHelper::ToFVector2D(Viewport->Size)) + .SupportsTransparency(EWindowTransparency::PerWindow) + .SizingRule(ESizingRule::UserSized) + .IsPopupWindow(bTooltipWindow || bPopupWindow) + .IsTopmostWindow(bTooltipWindow) + .FocusWhenFirstShown(!(Viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing)) + .HasCloseButton(false) + .SupportsMaximize(false) + .SupportsMinimize(false) + .CreateTitleBar(false) + .LayoutBorder(0) + .UserResizeBorder(0) + .UseOSWindowBorder(false) + [ + SAssignNew(Widget, SCogImguiWidget) + .Context(Context) + ]; + + if (ParentViewportData->Window.IsValid()) + { + FSlateApplication::Get().AddWindowAsNativeChild(Window, ParentViewportData->Window.Pin().ToSharedRef()); + } + else + { + FSlateApplication::Get().AddWindow(Window); + } + + FImGuiViewportData* ViewportData = new FImGuiViewportData(); + Viewport->PlatformUserData = ViewportData; + ViewportData->Context = ParentViewportData->Context; + ViewportData->Widget = Widget; + ViewportData->Window = Window; + + ParentViewportData->Context->WindowToViewportMap.Add(Window, Viewport->ID); + + Viewport->PlatformRequestResize = false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_DestroyWindow(ImGuiViewport* Viewport) +{ + FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData); + if (ViewportData == nullptr) + { + return; + } + + if ((Viewport->Flags & ImGuiViewportFlags_OwnedByApp)) + { + return; + } + + ViewportData->Context->WindowToViewportMap.Remove(ViewportData->Window); + + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + Window->RequestDestroyWindow(); + } + + delete ViewportData; + Viewport->PlatformUserData = nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_ShowWindow(ImGuiViewport* Viewport) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + Window->ShowWindow(); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_SetWindowPos(ImGuiViewport* Viewport, ImVec2 Pos) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + Window->MoveWindowTo(FCogImguiHelper::ToFVector2D(Pos)); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +ImVec2 FCogImguiContext::ImGui_GetWindowPos(ImGuiViewport* Viewport) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Widget = ViewportData->Widget.Pin()) + { + return FCogImguiHelper::ToImVec2(Widget->GetTickSpaceGeometry().GetAbsolutePosition()); + } + } + + return ImVec2(0, 0); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_SetWindowSize(ImGuiViewport* Viewport, ImVec2 Size) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + Window->Resize(FCogImguiHelper::ToFVector2D(Size)); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +ImVec2 FCogImguiContext::ImGui_GetWindowSize(ImGuiViewport* Viewport) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Widget = ViewportData->Widget.Pin()) + { + return FCogImguiHelper::ToImVec2(Widget->GetTickSpaceGeometry().GetAbsoluteSize()); + } + } + + return ImVec2(0, 0); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_SetWindowFocus(ImGuiViewport* Viewport) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + if (const TSharedPtr NativeWindow = Window->GetNativeWindow()) + { + NativeWindow->BringToFront(); + NativeWindow->SetWindowFocus(); + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogImguiContext::ImGui_GetWindowFocus(ImGuiViewport* Viewport) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + if (const TSharedPtr NativeWindow = Window->GetNativeWindow()) + { + return NativeWindow->IsForegroundWindow(); + } + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogImguiContext::ImGui_GetWindowMinimized(ImGuiViewport* Viewport) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + return Window->IsWindowMinimized(); + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_SetWindowTitle(ImGuiViewport* Viewport, const char* TitleAnsi) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + Window->SetTitle(FText::FromString(ANSI_TO_TCHAR(TitleAnsi))); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_SetWindowAlpha(ImGuiViewport* Viewport, float Alpha) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Window = ViewportData->Window.Pin()) + { + Window->SetOpacity(Alpha); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::ImGui_RenderWindow(ImGuiViewport* Viewport, void* Data) +{ + if (const FImGuiViewportData* ViewportData = static_cast(Viewport->PlatformUserData)) + { + if (const TSharedPtr Widget = ViewportData->Widget.Pin()) + { + Widget->SetDrawData(Viewport->DrawData); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +static UPlayerInput* GetPlayerInput(const UWorld* World) +{ + if (World == nullptr) + { + return nullptr; + } + + APlayerController* PlayerController = nullptr; + for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator) + { + APlayerController* ItPlayerController = Iterator->Get(); + if (ItPlayerController->IsLocalController()) + { + PlayerController = ItPlayerController; + break; + } + } + + if (PlayerController == nullptr) + { + return nullptr; + } + + UPlayerInput* PlayerInput = PlayerController->PlayerInput; + return PlayerInput; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::SetEnableInput(bool Value) +{ + bEnableInput = Value; + + if (bEnableInput == false) + { + TryGiveMouseCaptureBackToGame(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::SetShareMouse(bool Value) +{ + bShareMouse = Value; + + if (bEnableInput == false) + { + TryGiveMouseCaptureBackToGame(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::TryReleaseGameMouseCapture() +{ + if (bShareMouse) + { + return; + } + + if (TSharedPtr User = FSlateApplication::Get().GetCursorUser()) + { + if (User->HasCursorCapture()) + { + PreviousMouseCaptor = User->GetCursorCaptor(); + } + } + + if (ULocalPlayer* LocalPlayer = GetLocalPlayer()) + { + LocalPlayer->GetSlateOperations() + .ReleaseMouseLock() + .ReleaseMouseCapture(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::TryGiveMouseCaptureBackToGame() +{ + if (PreviousMouseCaptor.IsValid() == false) + { + return; + } + + TSharedRef PreviousMouseCaptorRef = PreviousMouseCaptor.Pin().ToSharedRef(); + + if (ULocalPlayer* LocalPlayer = GetLocalPlayer()) + { + LocalPlayer->GetSlateOperations().CaptureMouse(PreviousMouseCaptorRef); + } + + PreviousMouseCaptor.Reset(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::SetDPIScale(float Value) +{ + if (DpiScale == Value) + { + return; + } + + DpiScale = Value; + bRefreshDPIScale = true; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::RefreshDPIScale() +{ + bRefreshDPIScale = false; + + BuildFont(); + + ImGuiStyle NewStyle = ImGuiStyle(); + ImGui::GetStyle() = MoveTemp(NewStyle); + NewStyle.ScaleAllSizes(DpiScale); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::BuildFont() +{ + ImGuiIO& IO = ImGui::GetIO(); + IO.Fonts->Clear(); + + ImFontConfig FontConfig; + FontConfig.SizePixels = FMath::RoundFromZero(13.f * DpiScale); + IO.Fonts->AddFontDefault(&FontConfig); + + uint8* TextureDataRaw; + int32 TextureWidth, TextureHeight, BytesPerPixel; + IO.Fonts->GetTexDataAsRGBA32(&TextureDataRaw, &TextureWidth, &TextureHeight, &BytesPerPixel); + + UTexture2D* FontAtlasTexture = UTexture2D::CreateTransient(TextureWidth, TextureHeight, PF_R8G8B8A8, TEXT("ImGuiFontAtlas")); + FontAtlasTexture->Filter = TF_Bilinear; + FontAtlasTexture->AddressX = TA_Wrap; + FontAtlasTexture->AddressY = TA_Wrap; + + uint8* FontAtlasTextureData = static_cast(FontAtlasTexture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE)); + FMemory::Memcpy(FontAtlasTextureData, TextureDataRaw, TextureWidth * TextureHeight * BytesPerPixel); + FontAtlasTexture->GetPlatformData()->Mips[0].BulkData.Unlock(); + FontAtlasTexture->UpdateResource(); + + IO.Fonts->SetTexID(FontAtlasTexture); + FontAtlasTexturePtr.Reset(FontAtlasTexture); +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogImguiContext::IsConsoleOpened() const +{ + return GameViewport->ViewportConsole && GameViewport->ViewportConsole->ConsoleState != NAME_None; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::DrawDebug() +{ + if (ImGui::Begin("ImGui Integration Debug")) + { + ImGui::BeginDisabled(); + + ImVec2 AbsPos = FCogImguiHelper::ToImVec2(MainWidget->GetTickSpaceGeometry().GetAbsolutePosition()); + ImGui::InputFloat2("Widget Abs Pos", &AbsPos.x, "%0.1f"); + + ImVec2 AbsSize = FCogImguiHelper::ToImVec2(MainWidget->GetTickSpaceGeometry().GetAbsoluteSize()); + ImGui::InputFloat2("Widget Abs Size", &AbsSize.x, "%0.1f"); + + ImVec2 LocalSize = FCogImguiHelper::ToImVec2(MainWidget->GetTickSpaceGeometry().GetLocalSize()); + ImGui::InputFloat2("Widget Local Size", &LocalSize.x, "%0.1f"); + + FSlateApplication& SlateApp = FSlateApplication::Get(); + ImVec2 MousePosition = FCogImguiHelper::ToImVec2(SlateApp.GetCursorPos()); + ImGui::InputFloat2("Mouse", &MousePosition.x, "%0.1f"); + + ImGuiIO& IO = ImGui::GetIO(); + ImGui::InputFloat2("ImGui Mouse", &IO.MousePos.x, "%0.1f"); + + FString Focus = "None"; + if (TSharedPtr KeyboardFocusedWidget = SlateApp.GetKeyboardFocusedWidget()) + { + Focus = KeyboardFocusedWidget->ToString(); + } + static char Buffer[256] = ""; + ImStrncpy(Buffer, TCHAR_TO_ANSI(*Focus), IM_ARRAYSIZE(Buffer)); + ImGui::InputText("Keyboard Focus", Buffer, IM_ARRAYSIZE(Buffer)); + + ImGui::EndDisabled(); + } + ImGui::End(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +ULocalPlayer* FCogImguiContext::GetLocalPlayer() const +{ + if (GameViewport == nullptr) + { + return nullptr; + } + + UWorld* World = GameViewport->GetWorld(); + if (World == nullptr) + { + return nullptr; + } + + ULocalPlayer* LocalPlayer = World->GetFirstLocalPlayerFromController(); + return LocalPlayer; +} diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiDrawList.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiDrawList.cpp index c55e439..303dbcd 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiDrawList.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiDrawList.cpp @@ -1,66 +1,28 @@ #include "CogImguiDrawList.h" +#include "CogImguiHelper.h" + //-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiDrawList::CopyVertexData(TArray& OutVertexBuffer, const FTransform2D& Transform) const +FCogImguiDrawList::FCogImguiDrawList(ImDrawList* Source) { - // Reset and reserve space in destination buffer. - OutVertexBuffer.SetNumUninitialized(ImGuiVertexBuffer.Size, false); - - // Transform and copy vertex data. - for (int Idx = 0; Idx < ImGuiVertexBuffer.Size; Idx++) - { - const ImDrawVert& ImGuiVertex = ImGuiVertexBuffer[Idx]; - FSlateVertex& SlateVertex = OutVertexBuffer[Idx]; - - // Final UV is calculated in shader as XY * ZW, so we need set all components. - SlateVertex.TexCoords[0] = ImGuiVertex.uv.x; - SlateVertex.TexCoords[1] = ImGuiVertex.uv.y; - SlateVertex.TexCoords[2] = SlateVertex.TexCoords[3] = 1.f; - - const FVector2D VertexPosition = Transform.TransformPoint(FCogImguiHelper::ToVector2D(ImGuiVertex.pos)); - SlateVertex.Position[0] = VertexPosition.X; - SlateVertex.Position[1] = VertexPosition.Y; - - // Unpack ImU32 color. - SlateVertex.Color = FCogImguiHelper::UnpackImU32Color(ImGuiVertex.col); - } + VtxBuffer.swap(Source->VtxBuffer); + IdxBuffer.swap(Source->IdxBuffer); + CmdBuffer.swap(Source->CmdBuffer); + Flags = Source->Flags; } //-------------------------------------------------------------------------------------------------------------------------- -FCogImguiDrawList::DrawCommand FCogImguiDrawList::GetCommand(int CommandCount, const FTransform2D& Transform) const +FCogImguiDrawData::FCogImguiDrawData(const ImDrawData* Source) { - const ImDrawCmd& ImGuiCommand = ImGuiCommandBuffer[CommandCount]; - return - { - ImGuiCommand.ElemCount, - TransformRect(Transform, FCogImguiHelper::ToSlateRect(ImGuiCommand.ClipRect)), - FCogImguiHelper::ToTextureIndex(ImGuiCommand.TextureId) - }; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiDrawList::CopyIndexData(TArray& OutIndexBuffer, const int32 StartIndex, const int32 NumElements) const -{ - // Reset buffer. - OutIndexBuffer.SetNumUninitialized(NumElements, false); - - // Copy elements (slow copy because of different sizes of ImDrawIdx and SlateIndex and because SlateIndex can - // have different size on different platforms). - for (int i = 0; i < NumElements; i++) - { - OutIndexBuffer[i] = ImGuiIndexBuffer[StartIndex + i]; - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiDrawList::TransferDrawData(ImDrawList& Src) -{ - // Move data from source to this list. - Src.CmdBuffer.swap(ImGuiCommandBuffer); - Src.IdxBuffer.swap(ImGuiIndexBuffer); - Src.VtxBuffer.swap(ImGuiVertexBuffer); - - // ImGui seems to clear draw lists in every frame, but since source list can contain pointers to buffers that - // we just swapped, it is better to clear explicitly here. - Src._ResetForNewFrame(); + bValid = Source->Valid; + + TotalIdxCount = Source->TotalIdxCount; + TotalVtxCount = Source->TotalVtxCount; + + DrawLists.SetNumUninitialized(Source->CmdListsCount); + ConstructItems(DrawLists.GetData(), Source->CmdLists.Data, Source->CmdListsCount); + + DisplayPos = FCogImguiHelper::ToFVector2f(Source->DisplayPos); + DisplaySize = FCogImguiHelper::ToFVector2f(Source->DisplaySize); + FrameBufferScale = FCogImguiHelper::ToFVector2f(Source->FramebufferScale); } diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp index 4cb08cf..3299f36 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp @@ -31,7 +31,7 @@ ImGuiWindow* FCogImguiHelper::GetCurrentWindow() } //-------------------------------------------------------------------------------------------------------------------------- -FColor FCogImguiHelper::UnpackImU32Color(ImU32 Color) +FColor FCogImguiHelper::ToFColor(ImU32 Color) { return FColor { @@ -49,7 +49,13 @@ FSlateRect FCogImguiHelper::ToSlateRect(const ImVec4& Value) } //-------------------------------------------------------------------------------------------------------------------------- -FVector2D FCogImguiHelper::ToVector2D(const ImVec2& Value) +FVector2f FCogImguiHelper::ToFVector2f(const ImVec2& Value) +{ + return FVector2f(Value.x, Value.y); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FVector2D FCogImguiHelper::ToFVector2D(const ImVec2& Value) { return FVector2D(Value.x, Value.y); } @@ -90,7 +96,6 @@ ImVec4 FCogImguiHelper::ToImVec4(const FVector4f& Value) return ImVec4(Value.X, Value.Y, Value.Z, Value.W); } - //-------------------------------------------------------------------------------------------------------------------------- ImU32 FCogImguiHelper::ToImU32(const FColor& Value) { diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiModule.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiModule.cpp index 856d0bb..adf113f 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiModule.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiModule.cpp @@ -1,18 +1,11 @@ #include "CogImguiModule.h" -#include "CogImguiWidget.h" -#include "Engine/Engine.h" -#include "Engine/GameViewportClient.h" +#include "imgui.h" #include "HAL/LowLevelMemTracker.h" #include "HAL/UnrealMemory.h" -#include "Widgets/Layout/SScaleBox.h" #define LOCTEXT_NAMESPACE "FCogImguiModule" -//-------------------------------------------------------------------------------------------------------------------------- - -constexpr int32 Cog_ZOrder = 10000; - //-------------------------------------------------------------------------------------------------------------------------- static void* ImGui_MemAlloc(size_t Size, void* UserData) { @@ -37,64 +30,6 @@ void FCogImguiModule::ShutdownModule() { } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiModule::Initialize() -{ - TextureManager.InitializeErrorTexture(); - TextureManager.CreatePlainTexture("ImGuiModule_Plain", 2, 2, FColor::White); - - unsigned char* Pixels; - int Width, Height, Bpp; - - DefaultFontAtlas.Clear(); - DefaultFontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp); - const CogTextureIndex FontsTexureIndex = TextureManager.CreateTexture("ImGuiModule_FontAtlas", Width, Height, Bpp, Pixels); - DefaultFontAtlas.TexID = FCogImguiHelper::ToImTextureID(FontsTexureIndex); -} - -//-------------------------------------------------------------------------------------------------------------------------- -TSharedPtr FCogImguiModule::CreateImGuiWidget(UGameViewportClient* GameViewport, FCogImguiRenderFunction Render, ImFontAtlas* FontAtlas /*= nullptr*/) -{ - if (bIsInitialized == false) - { - Initialize(); - bIsInitialized = true; - } - - if (FontAtlas == nullptr) - { - FontAtlas = &DefaultFontAtlas; - } - - TSharedPtr ImguiWidget; - SAssignNew(ImguiWidget, SCogImguiWidget) - .GameViewport(GameViewport) - .FontAtlas(FontAtlas) - .Render(Render); - - GameViewport->AddViewportWidgetContent(ImguiWidget.ToSharedRef(), TNumericLimits::Max()); - - return ImguiWidget; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiModule::DestroyImGuiWidget(TSharedPtr ImGuiWidget) -{ - UGameViewportClient* Viewport = ImGuiWidget->GetGameViewport().Get(); - if (Viewport == nullptr) - { - return; - } - - TSharedPtr ParentWidget = ImGuiWidget->GetParentWidget(); - if (ParentWidget.IsValid() == false) - { - return; - } - - Viewport->RemoveViewportWidgetContent(ParentWidget.ToSharedRef()); -} - //-------------------------------------------------------------------------------------------------------------------------- #undef LOCTEXT_NAMESPACE diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiTextureManager.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiTextureManager.cpp deleted file mode 100644 index 2c323f0..0000000 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiTextureManager.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "CogImguiTextureManager.h" - -#include "Framework/Application/SlateApplication.h" - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiTextureManager::InitializeErrorTexture() -{ - CreatePlainTextureInternal(NAME_ErrorTexture, 2, 2, FColor::Magenta); -} - -//-------------------------------------------------------------------------------------------------------------------------- -CogTextureIndex FCogImguiTextureManager::CreateTexture(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, TFunction SrcDataCleanup) -{ - checkf(Name != NAME_None, TEXT("Trying to create a texture with a name 'NAME_None' is not allowed.")); - - return CreateTextureInternal(Name, Width, Height, SrcBpp, SrcData, SrcDataCleanup); -} - -//-------------------------------------------------------------------------------------------------------------------------- -CogTextureIndex FCogImguiTextureManager::CreatePlainTexture(const FName& Name, int32 Width, int32 Height, FColor Color) -{ - checkf(Name != NAME_None, TEXT("Trying to create a texture with a name 'NAME_None' is not allowed.")); - - return CreatePlainTextureInternal(Name, Width, Height, Color); -} - -//-------------------------------------------------------------------------------------------------------------------------- -CogTextureIndex FCogImguiTextureManager::CreateTextureResources(const FName& Name, UTexture2D* Texture) -{ - checkf(Name != NAME_None, TEXT("Trying to create texture resources with a name 'NAME_None' is not allowed.")); - checkf(Texture, TEXT("Null Texture.")); - - // Create an entry for the texture. - return AddTextureEntry(Name, Texture, false); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiTextureManager::ReleaseTextureResources(CogTextureIndex Index) -{ - checkf(IsInRange(Index), TEXT("Invalid texture index %d. Texture resources array has %d entries total."), Index, TextureResources.Num()); - - TextureResources[Index] = {}; -} - -//-------------------------------------------------------------------------------------------------------------------------- -CogTextureIndex FCogImguiTextureManager::CreateTextureInternal(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, TFunction SrcDataCleanup) -{ - // Create a texture. - UTexture2D* Texture = UTexture2D::CreateTransient(Width, Height); - - // Create a new resource for that texture. - Texture->UpdateResource(); - - // Update texture data. - FUpdateTextureRegion2D* TextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height); - auto DataCleanup = [SrcDataCleanup](uint8* Data, const FUpdateTextureRegion2D* UpdateRegion) - { - SrcDataCleanup(Data); - delete UpdateRegion; - }; - Texture->UpdateTextureRegions(0, 1u, TextureRegion, SrcBpp * Width, SrcBpp, SrcData, DataCleanup); - - // Create an entry for the texture. - if (Name == NAME_ErrorTexture) - { - ErrorTexture = { Name, Texture, true }; - return INDEX_ErrorTexture; - } - else - { - return AddTextureEntry(Name, Texture, true); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -CogTextureIndex FCogImguiTextureManager::CreatePlainTextureInternal(const FName& Name, int32 Width, int32 Height, const FColor& Color) -{ - // Create buffer with raw data. - const uint32 ColorPacked = Color.ToPackedARGB(); - const uint32 Bpp = sizeof(ColorPacked); - const uint32 SizeInPixels = Width * Height; - const uint32 SizeInBytes = SizeInPixels * Bpp; - uint8* SrcData = new uint8[SizeInBytes]; - std::fill(reinterpret_cast(SrcData), reinterpret_cast(SrcData) + SizeInPixels, ColorPacked); - auto SrcDataCleanup = [](uint8* Data) { delete[] Data; }; - - // Create new texture from raw data. - return CreateTextureInternal(Name, Width, Height, Bpp, SrcData, SrcDataCleanup); -} - -//-------------------------------------------------------------------------------------------------------------------------- -CogTextureIndex FCogImguiTextureManager::AddTextureEntry(const FName& Name, UTexture2D* Texture, bool bAddToRoot) -{ - // Try to find an entry with that name. - CogTextureIndex Index = FindTextureIndex(Name); - - // If this is a new name, try to find an entry to reuse. - if (Index == INDEX_NONE) - { - Index = FindTextureIndex(NAME_None); - } - - // Either update/reuse an entry or add a new one. - if (Index != INDEX_NONE) - { - TextureResources[Index] = { Name, Texture, bAddToRoot }; - return Index; - } - else - { - return TextureResources.Emplace(Name, Texture, bAddToRoot); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogImguiTextureManager::FTextureEntry::FTextureEntry(const FName& InName, UTexture2D* InTexture, bool bAddToRoot) - : Name(InName) -{ - checkf(InTexture, TEXT("Null texture.")); - - if (bAddToRoot) - { - // Get pointer only for textures that we added to root, so we can later release them. - Texture = InTexture; - // Add texture to the root to prevent garbage collection. - InTexture->AddToRoot(); - } - - // Create brush and resource handle for input texture. - Brush.SetResourceObject(InTexture); - CachedResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(Brush); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogImguiTextureManager::FTextureEntry::~FTextureEntry() -{ - Reset(true); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogImguiTextureManager::FTextureEntry& FCogImguiTextureManager::FTextureEntry::operator=(FTextureEntry&& Other) -{ - // Release old resources if allocated. - Reset(true); - - // Move data and ownership to this instance. - Name = MoveTemp(Other.Name); - Texture = MoveTemp(Other.Texture); - Brush = MoveTemp(Other.Brush); - CachedResourceHandle = MoveTemp(Other.CachedResourceHandle); - - // Reset the other entry (without releasing resources which are already moved to this instance) to remove tracks - // of ownership and mark it as empty/reusable. - Other.Reset(false); - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -const FSlateResourceHandle& FCogImguiTextureManager::FTextureEntry::GetResourceHandle() const -{ - if (!CachedResourceHandle.IsValid() && Brush.HasUObject()) - { - CachedResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(Brush); - } - return CachedResourceHandle; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiTextureManager::FTextureEntry::Reset(bool bReleaseResources) -{ - if (bReleaseResources) - { - // Release brush. - if (Brush.HasUObject() && FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().GetRenderer()->ReleaseDynamicResource(Brush); - } - - // Remove texture from root to allow for garbage collection (it might be invalid, if we never set it - // or this is an application shutdown). - if (Texture.IsValid()) - { - Texture->RemoveFromRoot(); - } - } - - // We use empty name to mark unused entries. - Name = NAME_None; - - // Clean fields to make sure that we don't reference released or moved resources. - Texture.Reset(); - Brush = FSlateNoResource(); - CachedResourceHandle = FSlateResourceHandle(); -} diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp index f170fad..a5a16f6 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp @@ -1,227 +1,32 @@ #include "CogImguiWidget.h" #include "CogImguiInputHelper.h" -#include "CogImguiInputHelper.h" -#include "CogImGuiInputProcessor.h" #include "CogImguiModule.h" -#include "CogImguiModule.h" -#include "CogImguiTextureManager.h" #include "CogImguiWidget.h" -#include "Engine/Console.h" -#include "Engine/GameViewportClient.h" -#include "Engine/LocalPlayer.h" -#include "Framework/Application/SlateApplication.h" -#include "GameFramework/PlayerController.h" #include "imgui.h" -#include "imgui_internal.h" -#include "implot.h" #include "SlateOptMacros.h" -#include "UnrealClient.h" -#include "Widgets/Layout/SBorder.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/SViewport.h" //-------------------------------------------------------------------------------------------------------------------------- BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SCogImguiWidget::Construct(const FArguments& InArgs) { - checkf(InArgs._GameViewport, TEXT("Null Game Viewport argument")); - - GameViewport = InArgs._GameViewport; - FontAtlas = InArgs._FontAtlas; - Render = InArgs._Render; + Context = InArgs._Context; SetVisibility(EVisibility::SelfHitTestInvisible); - - ImGuiContext = ImGui::CreateContext(FontAtlas); - ImPlotContext = ImPlot::CreateContext(); - ImPlot::SetImGuiContext(ImGuiContext); - - const char* InitFilenameTemp = TCHAR_TO_ANSI(*FCogImguiHelper::GetIniFilePath("imgui")); - ImStrncpy(IniFilename, InitFilenameTemp, IM_ARRAYSIZE(IniFilename)); - - ImGuiIO& IO = ImGui::GetIO(); - IO.IniFilename = IniFilename; - IO.DisplaySize = ImVec2(100, 100); - IO.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - IO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - IO.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; - IO.ConfigFlags |= ImGuiConfigFlags_NavNoCaptureKeyboard; - - //-------------------------------------------------------------------- - // Register input processor to forward input events to imgui - //-------------------------------------------------------------------- - if (FSlateApplication::IsInitialized()) - { - UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GameViewport->GetWorld()); - InputProcessor = MakeShared(PlayerInput, this); - FSlateApplication::Get().RegisterInputPreProcessor(InputProcessor.ToSharedRef(), 0); - } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION //-------------------------------------------------------------------------------------------------------------------------- SCogImguiWidget::~SCogImguiWidget() { - if (FSlateApplication::IsInitialized()) - { - if (InputProcessor.IsValid()) - { - FSlateApplication::Get().UnregisterInputPreProcessor(InputProcessor); - } - } - - DestroyImGuiContext(); } //-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::DestroyImGuiContext() +void SCogImguiWidget::SetDrawData(const ImDrawData* InDrawData) { - if (ImPlotContext != nullptr) - { - ImPlot::DestroyContext(ImPlotContext); - ImPlotContext = nullptr; - } - - if (ImGuiContext != nullptr) - { - ImGui::DestroyContext(ImGuiContext); - ImGuiContext = nullptr; - } + DrawData = FCogImguiDrawData(InDrawData); } -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) -{ - Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); - - TickKeyModifiers(); - TickFocus(); - TickImGui(InDeltaTime); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::TickKeyModifiers() -{ - //------------------------------------------------------------------------------------------------------- - // Refresh modifiers otherwise, when pressing ALT-TAB, the Alt modifier is always true - //------------------------------------------------------------------------------------------------------- - FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys(); - - ImGuiIO& IO = ImGui::GetIO(); - 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()); } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::TickImGui(float InDeltaTime) -{ - if (ImGuiContext == nullptr) - { - return; - } - - ImGui::SetCurrentContext(ImGuiContext); - ImPlot::SetImGuiContext(ImGuiContext); - ImPlot::SetCurrentContext(ImPlotContext); - - ImGuiIO& IO = ImGui::GetIO(); - IO.DeltaTime = FApp::GetDeltaTime(); - IO.DisplaySize = FCogImguiHelper::ToImVec2(GetTickSpaceGeometry().GetAbsoluteSize()); - - ImGui::NewFrame(); - - Render(InDeltaTime); - //DrawDebug(); - - ImGui::Render(); - - if ((IO.ConfigFlags & ImGuiConfigFlags_NoMouse) == 0) - { - SetCursor(FCogImguiInputHelper::ToSlateMouseCursor(ImGui::GetMouseCursor())); - } - - ImDrawData* DrawData = ImGui::GetDrawData(); - if (DrawData && DrawData->CmdListsCount > 0) - { - DrawLists.SetNum(DrawData->CmdListsCount, false); - for (int i = 0; i < DrawData->CmdListsCount; i++) - { - DrawLists[i].TransferDrawData(*DrawData->CmdLists[i]); - } - } - else - { - DrawLists.Empty(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::TickFocus() -{ - if (bEnableInput) - { - const auto& ViewportWidget = GameViewport->GetGameViewportWidget(); - if (!HasKeyboardFocus() && !IsConsoleOpened() && (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants())) - { - TakeFocus(); - } - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::TakeFocus() -{ - FSlateApplication& SlateApplication = FSlateApplication::Get(); - - PreviousUserFocusedWidget = SlateApplication.GetUserFocusedWidget(SlateApplication.GetUserIndexForKeyboard()); - - if (ULocalPlayer* LocalPlayer = GetLocalPlayer()) - { - TSharedRef FocusWidget = SharedThis(this); - LocalPlayer->GetSlateOperations().CaptureMouse(FocusWidget); - LocalPlayer->GetSlateOperations().SetUserFocus(FocusWidget); - } - else - { - SlateApplication.SetKeyboardFocus(SharedThis(this)); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::ReturnFocus() -{ - if (HasKeyboardFocus()) - { - auto FocusWidgetPtr = PreviousUserFocusedWidget.IsValid() - ? PreviousUserFocusedWidget.Pin() - : GameViewport->GetGameViewportWidget(); - - if (ULocalPlayer* LocalPlayer = GetLocalPlayer()) - { - auto FocusWidgetRef = FocusWidgetPtr.ToSharedRef(); - - if (FocusWidgetPtr == GameViewport->GetGameViewportWidget()) - { - LocalPlayer->GetSlateOperations().CaptureMouse(FocusWidgetRef); - } - - LocalPlayer->GetSlateOperations().SetUserFocus(FocusWidgetRef); - } - else - { - FSlateApplication& SlateApplication = FSlateApplication::Get(); - SlateApplication.ResetToDefaultPointerInputSettings(); - SlateApplication.SetUserFocus(SlateApplication.GetUserIndexForKeyboard(), FocusWidgetPtr); - } - } - - PreviousUserFocusedWidget.Reset(); -} - - //-------------------------------------------------------------------------------------------------------------------------- int32 SCogImguiWidget::OnPaint( const FPaintArgs& Args, @@ -233,36 +38,56 @@ int32 SCogImguiWidget::OnPaint( bool bParentEnabled) const { - const FSlateRenderTransform& WidgetToScreen = AllottedGeometry.GetAccumulatedRenderTransform().GetTranslation(); - const FSlateRenderTransform ImGuiToScreen = FCogImguiHelper::RoundTranslation(ImGuiRenderTransform.Concatenate(WidgetToScreen)); + const FSlateRenderTransform Transform(FCogImguiHelper::RoundTranslation(AllottedGeometry.GetAccumulatedRenderTransform().GetTranslation() - FVector2d(DrawData.DisplayPos))); - FCogImguiTextureManager& TextureManager = FCogImguiModule::Get().GetTextureManager(); - - for (const auto& DrawList : DrawLists) + FSlateBrush TextureBrush; + for (const FCogImguiDrawList& DrawList : DrawData.DrawLists) { - DrawList.CopyVertexData(VertexBuffer, ImGuiToScreen); - - int IndexBufferOffset = 0; - for (int i = 0; i < DrawList.NumCommands(); i++) + TArray Vertices; + Vertices.SetNumUninitialized(DrawList.VtxBuffer.Size); + for (int32 BufferIdx = 0; BufferIdx < Vertices.Num(); ++BufferIdx) { - const auto& DrawCommand = DrawList.GetCommand(i, ImGuiToScreen); + const ImDrawVert& Vtx = DrawList.VtxBuffer.Data[BufferIdx]; + Vertices[BufferIdx] = FSlateVertex::Make(Transform, FCogImguiHelper::ToFVector2f(Vtx.pos), FCogImguiHelper::ToFVector2f(Vtx.uv), FVector2f::UnitVector, FCogImguiHelper::ToFColor(Vtx.col)); + } - DrawList.CopyIndexData(IndexBuffer, IndexBufferOffset, DrawCommand.NumElements); + TArray Indices; + Indices.SetNumUninitialized(DrawList.IdxBuffer.Size); + for (int32 BufferIdx = 0; BufferIdx < Indices.Num(); ++BufferIdx) + { + Indices[BufferIdx] = DrawList.IdxBuffer.Data[BufferIdx]; + } - // Advance offset by number of copied elements to position it for the next command. - IndexBufferOffset += DrawCommand.NumElements; + for (const ImDrawCmd& DrawCmd : DrawList.CmdBuffer) + { + TArray VerticesSlice(Vertices.GetData() + DrawCmd.VtxOffset, Vertices.Num() - DrawCmd.VtxOffset); + TArray IndicesSlice(Indices.GetData() + DrawCmd.IdxOffset, DrawCmd.ElemCount); - // Get texture resource handle for this draw command (null index will be also mapped to a valid texture). - const FSlateResourceHandle& Handle = TextureManager.GetTextureHandle(DrawCommand.TextureId); + UTexture2D* Texture = DrawCmd.GetTexID(); + if (TextureBrush.GetResourceObject() != Texture) + { + TextureBrush.SetResourceObject(Texture); + if (IsValid(Texture)) + { + TextureBrush.ImageSize.X = Texture->GetSizeX(); + TextureBrush.ImageSize.Y = Texture->GetSizeY(); + TextureBrush.ImageType = ESlateBrushImageType::FullColor; + TextureBrush.DrawAs = ESlateBrushDrawType::Image; + } + else + { + TextureBrush.ImageSize.X = 0; + TextureBrush.ImageSize.Y = 0; + TextureBrush.ImageType = ESlateBrushImageType::NoImage; + TextureBrush.DrawAs = ESlateBrushDrawType::NoDrawType; + } + } - // Transform clipping rectangle to screen space and apply to elements that we draw. - const FSlateRect ClippingRect = DrawCommand.ClippingRect.IntersectionWith(MyClippingRect); - - OutDrawElements.PushClip(FSlateClippingZone{ ClippingRect }); - - // Add elements to the list. - FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0); + FSlateRect ClipRect(DrawCmd.ClipRect.x, DrawCmd.ClipRect.y, DrawCmd.ClipRect.z, DrawCmd.ClipRect.w); + ClipRect = TransformRect(Transform, ClipRect); + OutDrawElements.PushClip(FSlateClippingZone(ClipRect)); + FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, TextureBrush.GetRenderingResource(), VerticesSlice, IndicesSlice, nullptr, 0, 0); OutDrawElements.PopClip(); } } @@ -274,128 +99,22 @@ int32 SCogImguiWidget::OnPaint( FVector2D SCogImguiWidget::ComputeDesiredSize(float Scale) const { return FVector2D::ZeroVector; - //return Super::ComputeDesiredSize(Scale); } -//-------------------------------------------------------------------------------------------------------------------------- -ULocalPlayer* SCogImguiWidget::GetLocalPlayer() const -{ - if (GameViewport.IsValid() == false) - { - return nullptr; - } - - UWorld* World = GameViewport->GetWorld(); - if (World == nullptr) - { - return nullptr; - } - - ULocalPlayer* LocalPlayer = World->GetFirstLocalPlayerFromController(); - return LocalPlayer; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FVector2D SCogImguiWidget::TransformScreenPointToImGui(const FGeometry& MyGeometry, const FVector2D& Point) const -{ - const FSlateRenderTransform ImGuiToScreen = MyGeometry.GetAccumulatedRenderTransform(); - return ImGuiToScreen.Inverse().TransformPoint(Point); -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool SCogImguiWidget::IsConsoleOpened() const -{ - return GameViewport->ViewportConsole && GameViewport->ViewportConsole->ConsoleState != NAME_None; -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool SCogImguiWidget::IsCurrentContext() const -{ - return ImGui::GetCurrentContext() == ImGuiContext; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::SetAsCurrentContext() -{ - ImGui::SetCurrentContext(ImGuiContext); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::SetEnableInput(bool Value) -{ - bEnableInput = Value; - - if (bEnableInput) - { - TakeFocus(); - } - else - { - ReturnFocus(); - } - - ImGuiIO& IO = ImGui::GetIO(); - if (bEnableInput) - { - IO.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; - } - else - { - IO.ConfigFlags |= ImGuiConfigFlags_NoMouse; - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::SetDPIScale(float Value) -{ - if (DpiScale == Value) - { - return; - } - - DpiScale = Value; - OnDpiChanged(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::OnDpiChanged() -{ - if (FontAtlas == nullptr) - { - return; - } - - FontAtlas->Clear(); - - ImFontConfig FontConfig = {}; - FontConfig.SizePixels = FMath::RoundFromZero(13.f * DpiScale); - FontAtlas->AddFontDefault(&FontConfig); - - unsigned char* Pixels; - int Width, Height, Bpp; - FontAtlas->GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp); - const CogTextureIndex FontsTexureIndex = FCogImguiModule::Get().GetTextureManager().CreateTexture("xx", Width, Height, Bpp, Pixels); - FontAtlas->TexID = FCogImguiHelper::ToImTextureID(FontsTexureIndex); - - ImGuiStyle NewStyle = ImGuiStyle(); - ImGui::GetStyle() = MoveTemp(NewStyle); - NewStyle.ScaleAllSizes(DpiScale); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FReply SCogImguiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent) -{ - if (bEnableInput == false) - { - return FReply::Unhandled(); - } - - Super::OnFocusReceived(MyGeometry, FocusEvent); - - FSlateApplication::Get().ResetToDefaultPointerInputSettings(); - - return FReply::Handled(); -} +////-------------------------------------------------------------------------------------------------------------------------- +//FReply SCogImguiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent) +//{ +// if (bEnableInput == false) +// { +// return FReply::Unhandled(); +// } +// +// Super::OnFocusReceived(MyGeometry, FocusEvent); +// +// FSlateApplication::Get().ResetToDefaultPointerInputSettings(); +// +// return FReply::Handled(); +//} //-------------------------------------------------------------------------------------------------------------------------- FReply SCogImguiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) @@ -408,33 +127,4 @@ FReply SCogImguiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterE } -//-------------------------------------------------------------------------------------------------------------------------- -void SCogImguiWidget::DrawDebug() -{ - if (ImGui::Begin("ImGui Integration Debug")) - { - ImGui::BeginDisabled(); - - ImVec2 AbsPos = FCogImguiHelper::ToImVec2(GetTickSpaceGeometry().GetAbsolutePosition()); - ImGui::InputFloat2("Widget Abs Pos", &AbsPos.x, "%0.1f"); - - ImVec2 AbsSize = FCogImguiHelper::ToImVec2(GetTickSpaceGeometry().GetAbsoluteSize()); - ImGui::InputFloat2("Widget Abs Size", &AbsSize.x, "%0.1f"); - - ImVec2 LocalSize = FCogImguiHelper::ToImVec2(GetTickSpaceGeometry().GetLocalSize()); - ImGui::InputFloat2("Widget Local Size", &LocalSize.x, "%0.1f"); - - FSlateApplication& SlateApp = FSlateApplication::Get(); - ImVec2 MousePosition = FCogImguiHelper::ToImVec2(SlateApp.GetCursorPos()); - ImGui::InputFloat2("Mouse", &MousePosition.x, "%0.1f"); - - ImGuiIO& IO = ImGui::GetIO(); - ImGui::InputFloat2("ImGui Mouse", &IO.MousePos.x, "%0.1f"); - - ImGui::EndDisabled(); - } - ImGui::End(); -} - - diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImGuiInputProcessor.h b/Plugins/Cog/Source/CogImgui/Public/CogImGuiInputProcessor.h index c5d075c..508c874 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImGuiInputProcessor.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImGuiInputProcessor.h @@ -2,6 +2,7 @@ #include "Framework/Application/IInputProcessor.h" +class FCogImguiContext; class SCogImguiWidget; class UPlayerInput; enum ImGuiKey : int; @@ -11,7 +12,7 @@ class FImGuiInputProcessor : public IInputProcessor { public: - FImGuiInputProcessor(UPlayerInput* InPlayerInput, SCogImguiWidget* InWidget); + FImGuiInputProcessor(UPlayerInput* InPlayerInput, FCogImguiContext* InContext, SCogImguiWidget* InWidget); virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef SlateCursor) override; @@ -37,6 +38,8 @@ protected: bool HandleMouseButtonEvent(FSlateApplication& SlateApp, const FPointerEvent& Event, bool IsButtonDown); + bool IsMouseInsideMainViewport(); + void AddMousePosEvent(const FVector2D& MousePosition) const; bool IsKeyBoundToCommand(const FKeyEvent& KeyEvent); @@ -51,6 +54,8 @@ protected: static uint32 ToImGuiMouseButton(const FKey& MouseButton); + TObjectPtr Context = nullptr; + TObjectPtr PlayerInput = nullptr; TObjectPtr MainWidget = nullptr; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h new file mode 100644 index 0000000..62c6176 --- /dev/null +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h @@ -0,0 +1,117 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Texture2D.h" +#include "Templates/SharedPointer.h" +#include "UObject/StrongObjectPtr.h" +#include "imgui.h" + +class FCogImguiContext; +class IInputProcessor; +class SCogImguiWidget; +class SCogImguiWidget; +class SWindow; +class UGameViewportClient; +struct FDisplayMetrics; +struct ImPlotContext; + +struct COGIMGUI_API FImGuiViewportData +{ + TWeakPtr Window = nullptr; + TObjectPtr Context = nullptr; + TWeakPtr Widget = nullptr; +}; + +class COGIMGUI_API FCogImguiContext : public TSharedFromThis +{ +public: + void Initialize(); + + void Shutdown(); + + bool GetEnableInput() const { return bEnableInput; } + + void SetEnableInput(bool Value); + + bool GetShareMouse() const { return bShareMouse; } + + void SetShareMouse(bool Value); + + void BeginFrame(float InDeltaTime); + + void EndFrame(); + + float GetDpiScale() const { return DpiScale; } + + void SetDPIScale(float Value); + +private: + + void OnDisplayMetricsChanged(const FDisplayMetrics& DisplayMetrics) const; + + bool IsConsoleOpened() const; + + void DrawDebug(); + + void TryGiveMouseCaptureBackToGame(); + + void TryReleaseGameMouseCapture(); + + void RefreshDPIScale(); + + void BuildFont(); + + ULocalPlayer* GetLocalPlayer() const; + + static void ImGui_CreateWindow(ImGuiViewport* Viewport); + + static void ImGui_DestroyWindow(ImGuiViewport* Viewport); + + static void ImGui_ShowWindow(ImGuiViewport* Viewport); + + static void ImGui_SetWindowPos(ImGuiViewport* Viewport, ImVec2 Pos); + + static ImVec2 ImGui_GetWindowPos(ImGuiViewport* Viewport); + + static void ImGui_SetWindowSize(ImGuiViewport* Viewport, ImVec2 Size); + + static ImVec2 ImGui_GetWindowSize(ImGuiViewport* Viewport); + + static void ImGui_SetWindowFocus(ImGuiViewport* Viewport); + + static bool ImGui_GetWindowFocus(ImGuiViewport* Viewport); + + static bool ImGui_GetWindowMinimized(ImGuiViewport* Viewport); + + static void ImGui_SetWindowTitle(ImGuiViewport* Viewport, const char* TitleAnsi); + + static void ImGui_SetWindowAlpha(ImGuiViewport* Viewport, float Alpha); + + static void ImGui_RenderWindow(ImGuiViewport* Viewport, void* Data); + + TMap, ImGuiID> WindowToViewportMap; + + TSharedPtr InputProcessor = nullptr; + + TStrongObjectPtr FontAtlasTexturePtr = nullptr; + + TSharedPtr MainWidget = nullptr; + + TWeakPtr PreviousMouseCaptor = nullptr; + + TObjectPtr GameViewport = nullptr; + + ImGuiContext* ImGuiContext = nullptr; + + ImPlotContext* PlotContext = nullptr; + + char IniFilename[512] = {}; + + bool bEnableInput = false; + + bool bShareMouse = true; + + float DpiScale = 1.f; + + bool bRefreshDPIScale = false; +}; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h index 623ab5b..415f155 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h @@ -6,44 +6,39 @@ #include "Rendering/RenderingCommon.h" //-------------------------------------------------------------------------------------------------------------------------- -class FCogImguiDrawList +struct FCogImguiDrawList { -public: + FCogImguiDrawList() {}; + + FCogImguiDrawList(ImDrawList* Source); - struct DrawCommand - { - uint32 NumElements; - FSlateRect ClippingRect; - CogTextureIndex TextureId; - }; + ImVector VtxBuffer; + + ImVector IdxBuffer; + + ImVector CmdBuffer; - // Get the number of draw commands in this list. - FORCEINLINE int NumCommands() const { return ImGuiCommandBuffer.Size; } - - // Get the draw command by number. - // @param CommandNb - Number of draw command - // @param Transform - Transform to apply to clipping rectangle - // @returns Draw command data - DrawCommand GetCommand(int CommandCount, const FTransform2D& Transform) const; - - // Transform and copy vertex data to target buffer (old data in the target buffer are replaced). - // @param OutVertexBuffer - Destination buffer - // @param Transform - Transform to apply to all vertices - void CopyVertexData(TArray& OutVertexBuffer, const FTransform2D& Transform) const; - - // Transform and copy index data to target buffer (old data in the target buffer are replaced). - // Internal index buffer contains enough data to match the sum of NumElements from all draw commands. - // @param OutIndexBuffer - Destination buffer - // @param StartIndex - Start copying source data starting from this index - // @param NumElements - How many elements we want to copy - void CopyIndexData(TArray& OutIndexBuffer, const int32 StartIndex, const int32 NumElements) const; - - // Transfers data from ImGui source list to this object. Leaves source cleared. - void TransferDrawData(ImDrawList& Src); - -private: - - ImVector ImGuiCommandBuffer; - ImVector ImGuiIndexBuffer; - ImVector ImGuiVertexBuffer; + ImDrawListFlags Flags = ImDrawListFlags_None; }; + +//-------------------------------------------------------------------------------------------------------------------------- +struct FCogImguiDrawData +{ + FCogImguiDrawData() {}; + + FCogImguiDrawData(const ImDrawData* Source); + + bool bValid = false; + + int32 TotalIdxCount = 0; + + int32 TotalVtxCount = 0; + + TArray DrawLists; + + FVector2f DisplayPos = FVector2f::ZeroVector; + + FVector2f DisplaySize = FVector2f::ZeroVector; + + FVector2f FrameBufferScale = FVector2f::ZeroVector; +}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h index 858f6e9..b54f84a 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h @@ -20,11 +20,13 @@ public: static ImGuiWindow* GetCurrentWindow(); - static FColor UnpackImU32Color(ImU32 Color); + static FColor ToFColor(ImU32 Color); static FSlateRect ToSlateRect(const ImVec4& Value); - static FVector2D ToVector2D(const ImVec2& Value); + static FVector2f ToFVector2f(const ImVec2& Value); + + static FVector2D ToFVector2D(const ImVec2& Value); static ImVec2 ToImVec2(const FVector2D& Value); diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h index f5d95a6..73a474c 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h @@ -1,10 +1,6 @@ #pragma once #include "CoreMinimal.h" -#include "CogImguiWidget.h" -#include "CogImguiKeyInfo.h" -#include "CogImguiTextureManager.h" -#include "imgui.h" #include "Modules/ModuleManager.h" class COGIMGUI_API FCogImguiModule : public IModuleInterface @@ -22,22 +18,6 @@ public: virtual void StartupModule() override; virtual void ShutdownModule() override; - //---------------------------------------------------------------------------------------------------------------------- - TSharedPtr CreateImGuiWidget(UGameViewportClient* GameViewport, FCogImguiRenderFunction Render, ImFontAtlas* FontAtlas = nullptr); - - void DestroyImGuiWidget(TSharedPtr ImGuiWidget); - - FCogImguiTextureManager& GetTextureManager() { return TextureManager; } - - ImFontAtlas& GetDefaultFontAtlas() { return DefaultFontAtlas; } - private: - void Initialize(); - - FCogImguiTextureManager TextureManager; - - ImFontAtlas DefaultFontAtlas; - - bool bIsInitialized = false; }; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiTextureManager.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiTextureManager.h deleted file mode 100644 index e690465..0000000 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiTextureManager.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "CogImguiHelper.h" -#include "Engine/Texture2D.h" -#include "Styling/SlateBrush.h" -#include "Textures/SlateShaderResource.h" -#include "UObject/WeakObjectPtr.h" - -//-------------------------------------------------------------------------------------------------------------------------- -class FCogImguiTextureManager -{ -public: - - // Creates an empty manager. - FCogImguiTextureManager() = default; - - // Copying is disabled to protected resource ownership. - FCogImguiTextureManager(const FCogImguiTextureManager&) = delete; - FCogImguiTextureManager& operator=(const FCogImguiTextureManager&) = delete; - - // Moving transfers ownership and leaves source empty. - FCogImguiTextureManager(FCogImguiTextureManager&&) = delete; - FCogImguiTextureManager& operator=(FCogImguiTextureManager&&) = delete; - - void InitializeErrorTexture(); - - // Find texture index by name. - // @param Name - The name of a texture to find - // @returns The index of a texture with given name or INDEX_NONE if there is no such texture - CogTextureIndex FindTextureIndex(const FName& Name) const - { - return TextureResources.IndexOfByPredicate([&](const auto& Entry) { return Entry.GetName() == Name; }); - } - - // Get the name of a texture at given index. Returns NAME_None, if index is out of range. - // @param Index - Index of a texture - // @returns The name of a texture at given index or NAME_None if index is out of range. - FName GetTextureName(CogTextureIndex Index) const - { - return IsInRange(Index) ? TextureResources[Index].GetName() : NAME_None; - } - - // Get the Slate Resource Handle to a texture at given index. If index is out of range or resources are not valid - // it returns a handle to the error texture. - // @param Index - Index of a texture - // @returns The Slate Resource Handle for a texture at given index or to error texture, if no valid resources were - // found at given index - const FSlateResourceHandle& GetTextureHandle(CogTextureIndex Index) const - { - return IsValidTexture(Index) ? TextureResources[Index].GetResourceHandle() : ErrorTexture.GetResourceHandle(); - } - - // Create a texture from raw data. - // @param Name - The texture name - // @param Width - The texture width - // @param Height - The texture height - // @param SrcBpp - The size in bytes of one pixel - // @param SrcData - The source data - // @param SrcDataCleanup - Optional function called to release source data after texture is created (only needed, if data need to be released) - // @returns The index of a texture that was created - CogTextureIndex CreateTexture(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, TFunction SrcDataCleanup = [](uint8*) {}); - - // Create a plain texture. - // @param Name - The texture name - // @param Width - The texture width - // @param Height - The texture height - // @param Color - The texture color - // @returns The index of a texture that was created - CogTextureIndex CreatePlainTexture(const FName& Name, int32 Width, int32 Height, FColor Color); - - // Create Slate resources to an existing texture, managed externally. - // @param Name - The texture name - // @param Texture - The texture - // @returns The index to created/updated texture resources - CogTextureIndex CreateTextureResources(const FName& Name, UTexture2D* Texture); - - // Release resources for given texture. Ignores invalid indices. - // @param Index - The index of a texture resources - void ReleaseTextureResources(CogTextureIndex Index); - -private: - - // See CreateTexture for general description. - // Internal implementations doesn't validate name or resource uniqueness. Instead it uses NAME_ErrorTexture - // (aka NAME_None) and INDEX_ErrorTexture (aka INDEX_NONE) to identify ErrorTexture. - CogTextureIndex CreateTextureInternal(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, TFunction SrcDataCleanup = [](uint8*) {}); - - // See CreatePlainTexture for general description. - // Internal implementations doesn't validate name or resource uniqueness. Instead it uses NAME_ErrorTexture - // (aka NAME_None) and INDEX_ErrorTexture (aka INDEX_NONE) to identify ErrorTexture. - CogTextureIndex CreatePlainTextureInternal(const FName& Name, int32 Width, int32 Height, const FColor& Color); - - // Add or reuse texture entry. - // @param Name - The texture name - // @param Texture - The texture - // @param bAddToRoot - If true, we should add texture to root to prevent garbage collection (use for own textures) - // @returns The index of the entry that we created or reused - CogTextureIndex AddTextureEntry(const FName& Name, UTexture2D* Texture, bool bAddToRoot); - - // Check whether index is in range allocated for TextureResources (it doesn't mean that resources are valid). - FORCEINLINE bool IsInRange(CogTextureIndex Index) const - { - return static_cast(Index) < static_cast(TextureResources.Num()); - } - - // Check whether index is in range and whether texture resources are valid (using NAME_None sentinel). - FORCEINLINE bool IsValidTexture(CogTextureIndex Index) const - { - return IsInRange(Index) && TextureResources[Index].GetName() != NAME_None; - } - - // Entry for texture resources. Only supports explicit construction. - struct FTextureEntry - { - FTextureEntry() = default; - FTextureEntry(const FName& InName, UTexture2D* InTexture, bool bAddToRoot); - ~FTextureEntry(); - - // Copying is not supported. - FTextureEntry(const FTextureEntry&) = delete; - FTextureEntry& operator=(const FTextureEntry&) = delete; - - // We rely on TArray and don't implement custom move constructor... - FTextureEntry(FTextureEntry&&) = delete; - // ... but we need move assignment to support reusing entries. - FTextureEntry& operator=(FTextureEntry&& Other); - - const FName& GetName() const { return Name; } - const FSlateResourceHandle& GetResourceHandle() const; - - private: - - void Reset(bool bReleaseResources); - - FName Name = NAME_None; - mutable FSlateResourceHandle CachedResourceHandle; - TWeakObjectPtr Texture; - FSlateBrush Brush; - }; - - TArray TextureResources; - FTextureEntry ErrorTexture; - - static constexpr EName NAME_ErrorTexture = NAME_None; - static constexpr CogTextureIndex INDEX_ErrorTexture = INDEX_NONE; -}; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h index de15ccc..2aa0c5a 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h @@ -7,14 +7,8 @@ #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SLeafWidget.h" -class IInputProcessor; +class FCogImguiContext; class UGameViewportClient; -class ULocalPlayer; -struct ImFontAtlas; -struct ImGuiContext; -struct ImPlotContext; - -using FCogImguiRenderFunction = TFunction; //-------------------------------------------------------------------------------------------------------------------------- class COGIMGUI_API SCogImguiWidget : public SLeafWidget @@ -24,81 +18,26 @@ class COGIMGUI_API SCogImguiWidget : public SLeafWidget public: SLATE_BEGIN_ARGS(SCogImguiWidget) {} - SLATE_ARGUMENT(UGameViewportClient*, GameViewport) - SLATE_ARGUMENT(ImFontAtlas*, FontAtlas) - SLATE_ARGUMENT(FCogImguiRenderFunction, Render) + SLATE_ARGUMENT(FCogImguiContext*, Context) SLATE_END_ARGS() void Construct(const FArguments& InArgs); ~SCogImguiWidget(); - //---------------------------------------------------------------------------------------------------- - // SWidget overrides - //---------------------------------------------------------------------------------------------------- - 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; virtual bool SupportsKeyboardFocus() const override { return true; } virtual FReply OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) override; - virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent) override; - virtual FVector2D ComputeDesiredSize(float Scale) const override; - ULocalPlayer* GetLocalPlayer() const; - - bool GetEnableInput() const { return bEnableInput; } - - void SetEnableInput(bool Value); - - bool GetShareMouse() const { return bShareMouse; } - - void SetShareMouse(bool Value) { bShareMouse = Value; } - - float GetDpiScale() const { return DpiScale; } - - void SetDPIScale(float Value); - - bool IsCurrentContext() const; - - void SetAsCurrentContext(); - - TWeakObjectPtr GetGameViewport() const { return GameViewport; } - - void DestroyImGuiContext(); + void SetDrawData(const ImDrawData* InDrawData); protected: - FVector2D TransformScreenPointToImGui(const FGeometry& MyGeometry, const FVector2D& Point) const; - - virtual void TickKeyModifiers(); - - virtual void TickImGui(float InDeltaTime); - - virtual void TickFocus(); - - virtual void TakeFocus(); - - virtual void ReturnFocus(); - - virtual void OnDpiChanged(); - - virtual bool IsConsoleOpened() const; - - virtual void DrawDebug(); - - TWeakObjectPtr GameViewport; - - ImFontAtlas* FontAtlas = nullptr; - - TWeakPtr PreviousUserFocusedWidget; - - bool bEnableInput = false; - - bool bShareMouse = true; + TObjectPtr Context = nullptr; FSlateRenderTransform ImGuiRenderTransform; @@ -106,17 +45,5 @@ protected: mutable TArray IndexBuffer; - TArray DrawLists; - - ImGuiContext* ImGuiContext = nullptr; - - ImPlotContext* ImPlotContext = nullptr; - - FCogImguiRenderFunction Render; - - float DpiScale = 1.f; - - char IniFilename[512]; - - TSharedPtr InputProcessor = nullptr; + FCogImguiDrawData DrawData; }; diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindow.cpp index 5e3586d..65fbff2 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow.cpp +++ b/Plugins/Cog/Source/CogWindow/Private/CogWindow.cpp @@ -2,6 +2,7 @@ #include "CogDebugDraw.h" #include "CogDebugSettings.h" +#include "CogWindow_Settings.h" #include "CogWindowManager.h" #include "CogWindowWidgets.h" #include "Engine/World.h" @@ -75,7 +76,7 @@ void FCogWindow::Render(float DeltaTime) ImGui::PopStyleVar(1); } - if (GetOwner()->GetShowHelp()) + if (GetOwner()->GetSettingsWindow()->GetSettingsConfig()->bShowHelp) { if (ImGui::IsItemHovered()) { diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindowManager.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindowManager.cpp index f102cbc..7818efd 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindowManager.cpp +++ b/Plugins/Cog/Source/CogWindow/Private/CogWindowManager.cpp @@ -1,9 +1,9 @@ #include "CogWindowManager.h" #include "CogDebugDrawImGui.h" +#include "CogImguiHelper.h" #include "CogImguiInputHelper.h" #include "CogImguiModule.h" -#include "CogWindow_Inputs.h" #include "CogWindow_Layouts.h" #include "CogWindow_Settings.h" #include "CogWindow_Spacing.h" @@ -41,7 +41,7 @@ void UCogWindowManager::PostInitProperties() //-------------------------------------------------------------------------------------------------------------------------- void UCogWindowManager::InitializeInternal() { - ImGuiWidget = FCogImguiModule::Get().CreateImGuiWidget(GEngine->GameViewport, [this](float DeltaTime) { Render(DeltaTime); }); + Context.Initialize(); ImGuiSettingsHandler IniHandler; IniHandler.TypeName = "Cog"; @@ -59,7 +59,6 @@ void UCogWindowManager::InitializeInternal() SpaceWindows.Add(AddWindow("Spacing 3", false)); SpaceWindows.Add(AddWindow("Spacing 4", false)); - InputsWindow = AddWindow("Window.Inputs", false); LayoutsWindow = AddWindow("Window.Layouts", false); SettingsWindow = AddWindow("Window.Settings", false); @@ -86,7 +85,8 @@ void UCogWindowManager::InitializeInternal() TEXT("Save the layout. Cog.SaveLayout "), FConsoleCommandWithArgsDelegate::CreateLambda([this](const TArray& Args) { if (Args.Num() > 0) { SaveLayout(FCString::Atoi(*Args[0])); }}), ECVF_Cheat)); - + + IsInitialized = true; } //-------------------------------------------------------------------------------------------------------------------------- @@ -105,7 +105,7 @@ void UCogWindowManager::Shutdown() // Destroy ImGui before destroying the windows to make sure // imgui serialize their visibility state in imgui.ini //------------------------------------------------------------ - ImGuiWidget->DestroyImGuiContext(); + Context.Shutdown(); SaveConfig(); @@ -125,8 +125,6 @@ void UCogWindowManager::Shutdown() { IConsoleManager::Get().UnregisterConsoleObject(ConsoleCommand); } - - FCogImguiModule::Get().DestroyImGuiWidget(ImGuiWidget); } //-------------------------------------------------------------------------------------------------------------------------- @@ -137,7 +135,7 @@ void UCogWindowManager::Tick(float DeltaTime) return; } - if (ImGuiWidget.IsValid() == false) + if (IsInitialized == false) { InitializeInternal(); } @@ -149,16 +147,14 @@ void UCogWindowManager::Tick(float DeltaTime) LayoutToLoad = -1; } - if (bRefreshDPIScale) - { - RefreshDPIScale(); - bRefreshDPIScale = false; - } - for (FCogWindow* Window : Windows) { Window->GameTick(DeltaTime); } + + Context.BeginFrame(DeltaTime); + Render(DeltaTime); + Context.EndFrame(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -168,7 +164,7 @@ void UCogWindowManager::Render(float DeltaTime) ImGui::DockSpaceOverViewport(0, ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingInCentralNode | ImGuiDockNodeFlags_AutoHideTabBar); ImGui::PopStyleColor(1); - bool bCompactSaved = bCompactMode; + bool bCompactSaved = SettingsWindow->GetSettingsConfig()->bCompactMode; if (bCompactSaved) { FCogWindowWidgets::PushStyleCompact(); @@ -176,7 +172,7 @@ void UCogWindowManager::Render(float DeltaTime) if (bHideAllWindows == false) { - if (ImGuiWidget->GetEnableInput() || bHideMainMenuOnGameInput == false) + if (Context.GetEnableInput()) { RenderMainMenu(); } @@ -188,7 +184,7 @@ void UCogWindowManager::Render(float DeltaTime) if (Window->GetIsVisible() && bHideAllWindows == false) { - if (bTransparentMode) + if (SettingsWindow->GetSettingsConfig()->bTransparentMode) { ImGui::SetNextWindowBgAlpha(0.35f); } @@ -202,8 +198,6 @@ void UCogWindowManager::Render(float DeltaTime) FCogWindowWidgets::PopStyleCompact(); } - TickDPI(); - FCogDebugDrawImGui::Draw(); } @@ -354,8 +348,6 @@ void UCogWindowManager::RenderMainMenu() ImGui::Separator(); - RenderMenuItem(*InputsWindow, "Inputs"); - RenderMenuItem(*LayoutsWindow, "Layouts"); RenderMenuItem(*SettingsWindow, "Settings"); @@ -508,7 +500,7 @@ void UCogWindowManager::RenderOptionMenu(UCogWindowManager::FMenu& Menu) //-------------------------------------------------------------------------------------------------------------------------- void UCogWindowManager::RenderMenuItem(FCogWindow& Window, const char* MenuItemName) { - if (bShowWindowsInMainMenu) + if (SettingsWindow->GetSettingsConfig()->bShowWindowsInMainMenu) { ImGui::SetNextWindowSizeConstraints( ImVec2(FCogWindowWidgets::GetFontWidth() * 40, ImGui::GetTextLineHeightWithSpacing() * 5), @@ -535,31 +527,7 @@ void UCogWindowManager::RenderMenuItem(FCogWindow& Window, const char* MenuItemN } } -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::TickDPI() -{ - const float MouseWheel = ImGui::GetIO().MouseWheel; - const bool IsControlDown = ImGui::GetIO().KeyCtrl; - if (IsControlDown && MouseWheel != 0) - { - SetDPIScale(FMath::Clamp(DPIScale + (MouseWheel > 0 ? 0.1f : -0.1f), 0.5f, 2.0f)); - } - const bool IsMiddleMouseClicked = ImGui::GetIO().MouseClicked[2]; - if (IsControlDown && IsMiddleMouseClicked) - { - SetDPIScale(1.0f); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::RefreshDPIScale() -{ - if (SCogImguiWidget* Widget = ImGuiWidget.Get()) - { - Widget->SetDPIScale(DPIScale); - } -} //-------------------------------------------------------------------------------------------------------------------------- void UCogWindowManager::SettingsHandler_ClearAll(ImGuiContext* Context, ImGuiSettingsHandler* Handler) @@ -652,18 +620,6 @@ void UCogWindowManager::SettingsHandler_WriteAll(ImGuiContext* Context, ImGuiSet Buffer->append("\n"); } -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SetDPIScale(float Value) -{ - if (DPIScale == Value) - { - return; - } - - DPIScale = Value; - bRefreshDPIScale = true; -} - //-------------------------------------------------------------------------------------------------------------------------- void UCogWindowManager::ResetAllWindowsConfig() { @@ -786,5 +742,5 @@ const UObject* UCogWindowManager::GetAsset(const TSubclassOf AssetClass void UCogWindowManager::ToggleInputMode() { UE_LOG(LogCogImGui, Verbose, TEXT("UCogWindowManager::ToggleInputMode")); - ImGuiWidget->SetEnableInput(!ImGuiWidget->GetEnableInput()); + Context.SetEnableInput(!Context.GetEnableInput()); } \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Inputs.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindow_Inputs.cpp index 1169b81..5a44d55 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Inputs.cpp +++ b/Plugins/Cog/Source/CogWindow/Private/CogWindow_Inputs.cpp @@ -1,89 +1,2 @@ #include "CogWindow_Inputs.h" -#include "CogImguiInputHelper.h" -#include "CogImguiWidget.h" -#include "CogWindowManager.h" -#include "CogWindowWidgets.h" -#include "InputCoreTypes.h" -#include "imgui.h" - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Inputs::Initialize() -{ - Super::Initialize(); - - bHasMenu = false; - - Config = GetConfig(); - - SCogImguiWidget* ImGuiWidget = GetOwner()->GetImGuiWidget().Get(); - ImGuiWidget->SetEnableInput(Config->bEnableInput); - - ImGuiIO& IO = ImGui::GetIO(); - FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard, Config->bNavEnableKeyboard); - FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad, Config->bNavEnableGamepad); - FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_NavNoCaptureKeyboard, Config->bNavNoCaptureInput); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Inputs::PreSaveConfig() -{ - Super::PreSaveConfig(); - - ImGuiIO& IO = ImGui::GetIO(); - Config->bNavEnableKeyboard = IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard; - Config->bNavEnableGamepad = IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad; - Config->bNavNoCaptureInput = IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard; - - if (SCogImguiWidget* ImGuiWidget = GetOwner()->GetImGuiWidget().Get()) - { - Config->bEnableInput = ImGuiWidget->GetEnableInput(); - Config->bShareMouse = ImGuiWidget->GetShareMouse(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Inputs::RenderContent() -{ - const UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GetWorld()); - if (PlayerInput == nullptr) - { - return; - } - - SCogImguiWidget* ImGuiWidget = GetOwner()->GetImGuiWidget().Get(); - if (ImGuiWidget == nullptr) - { - return; - } - - bool bEnableInput = ImGuiWidget->GetEnableInput(); - if (ImGui::Checkbox("Enable Input", &bEnableInput)) - { - ImGuiWidget->SetEnableInput(bEnableInput); - } - - const char* ShortcutText = TCHAR_TO_ANSI(*FCogImguiInputHelper::CommandToString(PlayerInput, UCogWindowManager::ToggleInputCommand)); - const float ShortcutWidth = (ShortcutText && ShortcutText[0]) ? ImGui::CalcTextSize(ShortcutText, NULL).x : 0.0f; - if (ShortcutWidth > 0.0f) - { - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - ShortcutWidth); - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); - ImGui::Text(ShortcutText); - ImGui::PopStyleColor(); - } - - ImGui::Separator(); - - bool bShareMouse = ImGuiWidget->GetShareMouse(); - if (ImGui::Checkbox("Share Mouse", &bShareMouse)) - { - ImGuiWidget->SetShareMouse(bShareMouse); - } - - ImGuiIO& IO = ImGui::GetIO(); - ImGui::CheckboxFlags("Keyboard Navigation", &IO.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); - ImGui::CheckboxFlags("Gamepad Navigation", &IO.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); - ImGui::CheckboxFlags("Navigation No Capture", &IO.ConfigFlags, ImGuiConfigFlags_NavNoCaptureKeyboard); -} diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Settings.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindow_Settings.cpp index 59d92e9..15ed0b6 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Settings.cpp +++ b/Plugins/Cog/Source/CogWindow/Private/CogWindow_Settings.cpp @@ -1,8 +1,12 @@ #include "CogWindow_Settings.h" +#include "CogImguiHelper.h" +#include "CogImguiInputHelper.h" #include "CogImguiModule.h" +#include "CogImguiWidget.h" #include "CogWindowManager.h" #include "CogWindowWidgets.h" +#include "imgui.h" #include "InputCoreTypes.h" //-------------------------------------------------------------------------------------------------------------------------- @@ -11,17 +15,86 @@ void FCogWindow_Settings::Initialize() Super::Initialize(); bHasMenu = false; + + Config = GetConfig(); + + ImGuiIO& IO = ImGui::GetIO(); + FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_ViewportsEnable, Config->bEnableViewports); + + GetOwner()->GetContext().SetDPIScale(Config->DPIScale); + + FCogImguiContext& Context = GetOwner()->GetContext(); + Context.SetEnableInput(Config->bEnableInput); + Context.SetShareMouse(Config->bShareMouse); + + FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard, Config->bNavEnableKeyboard); + FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad, Config->bNavEnableGamepad); + FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_NavNoCaptureKeyboard, Config->bNavNoCaptureInput); + FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange, Config->bNoMouseCursorChange); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Settings::PreSaveConfig() +{ + Super::PreSaveConfig(); + + ImGuiIO& IO = ImGui::GetIO(); + Config->bNavEnableKeyboard = IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard; + Config->bNavEnableGamepad = IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad; + Config->bNavNoCaptureInput = IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard; + Config->bNoMouseCursorChange = IO.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange; + + const FCogImguiContext& Context = GetOwner()->GetContext(); + Config->bEnableInput = Context.GetEnableInput(); + Config->bShareMouse = Context.GetShareMouse(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Settings::ResetConfig() +{ + Super::ResetConfig(); + + Config->Reset(); } //-------------------------------------------------------------------------------------------------------------------------- void FCogWindow_Settings::RenderContent() { + const UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GetWorld()); + if (PlayerInput == nullptr) + { + return; + } + + ImGuiIO& IO = ImGui::GetIO(); + + FCogImguiContext& Context = GetOwner()->GetContext(); + bool bEnableInput = Context.GetEnableInput(); + if (ImGui::Checkbox("Enable Input", &bEnableInput)) + { + Context.SetEnableInput(bEnableInput); + } + + const char* ShortcutText = TCHAR_TO_ANSI(*FCogImguiInputHelper::CommandToString(PlayerInput, UCogWindowManager::ToggleInputCommand)); + const float ShortcutWidth = (ShortcutText && ShortcutText[0]) ? ImGui::CalcTextSize(ShortcutText, NULL).x : 0.0f; + if (ShortcutWidth > 0.0f) + { + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - ShortcutWidth); + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); + ImGui::Text(ShortcutText); + ImGui::PopStyleColor(); + } + + ImGui::Separator(); + FCogWindowWidgets::SetNextItemToShortWidth(); - ImGui::SliderFloat("DPI Scale", &GetOwner()->DPIScale, 0.5f, 2.0f, "%.1f"); + ImGui::SliderFloat("DPI Scale", &Config->DPIScale, 0.5f, 2.0f, "%.1f"); if (ImGui::IsItemDeactivatedAfterEdit()) { - GetOwner()->bRefreshDPIScale = true; + SetDPIScale(Config->DPIScale); } + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); @@ -30,20 +103,63 @@ void FCogWindow_Settings::RenderContent() ImGui::EndTooltip(); } - ImGui::Checkbox("Compact Mode", &GetOwner()->bCompactMode); + ImGui::Separator(); - ImGui::Checkbox("Show Windows In Main Menu", &GetOwner()->bShowWindowsInMainMenu); + if (ImGui::Checkbox("Enable Viewports", &Config->bEnableViewports)) + { + FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_ViewportsEnable, Config->bEnableViewports); + } - ImGui::Checkbox("Hide Main Menu On Game Input", &GetOwner()->bHideMainMenuOnGameInput); + ImGui::Checkbox("Compact Mode", &Config->bCompactMode); - ImGui::Checkbox("Show Window Help", &GetOwner()->bShowHelp); + ImGui::Checkbox("Show Windows In Main Menu", &Config->bShowWindowsInMainMenu); + + ImGui::Checkbox("Show Window Help", &Config->bShowHelp); + + ImGui::Separator(); + + bool bShareMouse = Context.GetShareMouse(); + if (ImGui::Checkbox("Share Mouse", &bShareMouse)) + { + Context.SetShareMouse(bShareMouse); + } + + ImGui::CheckboxFlags("Keyboard Navigation", &IO.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); + ImGui::CheckboxFlags("Gamepad Navigation", &IO.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); + ImGui::CheckboxFlags("Navigation No Capture", &IO.ConfigFlags, ImGuiConfigFlags_NavNoCaptureKeyboard); + ImGui::CheckboxFlags("No Mouse Cursor Change", &IO.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); ImGui::Separator(); - ImGui::Spacing(); - ImGui::Spacing(); if (ImGui::Button("Reset All Windows Config")) { GetOwner()->ResetAllWindowsConfig(); } + +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Settings::RenderTick(float DeltaTime) +{ + Super::RenderTick(DeltaTime); + + const float MouseWheel = ImGui::GetIO().MouseWheel; + const bool IsControlDown = ImGui::GetIO().KeyCtrl; + if (IsControlDown && MouseWheel != 0) + { + SetDPIScale(FMath::Clamp(Config->DPIScale + (MouseWheel > 0 ? 0.1f : -0.1f), 0.5f, 2.0f)); + } + + const bool IsMiddleMouseClicked = ImGui::GetIO().MouseClicked[2]; + if (IsControlDown && IsMiddleMouseClicked) + { + SetDPIScale(1.0f); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Settings::SetDPIScale(float Value) +{ + Config->DPIScale = Value; + GetOwner()->GetContext().SetDPIScale(Config->DPIScale); } diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindowManager.h b/Plugins/Cog/Source/CogWindow/Public/CogWindowManager.h index bb5a960..8abf719 100644 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindowManager.h +++ b/Plugins/Cog/Source/CogWindow/Public/CogWindowManager.h @@ -1,12 +1,11 @@ #pragma once #include "CoreMinimal.h" -#include "CogImguiWidget.h" +#include "CogImguiContext.h" #include "imgui.h" #include "CogWindowManager.generated.h" class FCogWindow; -class FCogWindow_Inputs; class FCogWindow_Layouts; class FCogWindow_Settings; class IConsoleObject; @@ -55,26 +54,12 @@ public: virtual void SetHideAllWindows(bool Value); - virtual float GetDPIScale() const { return DPIScale; } - - virtual void SetDPIScale(float Value); - - virtual bool GetCompactMode() const { return bCompactMode; } - - virtual void SetCompactMode(bool Value) { bCompactMode = Value; } - - virtual bool GetShowHelp() const { return bShowHelp; } - - virtual void SetShowHelp(bool Value) { bShowHelp = Value; } - - virtual bool GetPreviewWindowsInMenu() const { return bShowWindowsInMainMenu; } - - virtual void SetPreviewWindowsInMenu(bool Value) { bShowWindowsInMainMenu = Value; } - virtual void ResetAllWindowsConfig(); virtual bool RegisterDefaultCommandBindings(); + const FCogWindow_Settings* GetSettingsWindow() const { return SettingsWindow; } + UCogWindowConfig* GetConfig(const TSubclassOf ConfigClass); template @@ -85,7 +70,9 @@ public: template T* GetAsset(); - TSharedPtr GetImGuiWidget() const { return ImGuiWidget; } + const FCogImguiContext& GetContext() const { return Context; } + + FCogImguiContext& GetContext() { return Context; } static void AddCommand(UPlayerInput* PlayerInput, const FString& Command, const FKey& Key); @@ -93,7 +80,6 @@ public: protected: - friend class FCogWindow_Inputs; friend class FCogWindow_Layouts; friend class FCogWindow_Settings; @@ -106,8 +92,6 @@ protected: virtual void InitializeInternal(); - virtual void RefreshDPIScale(); - virtual void RenderMainMenu(); virtual FMenu* AddMenu(const FString& Name); @@ -116,8 +100,6 @@ protected: virtual void RenderMenuItem(FCogWindow& Window, const char* MenuItemName); - virtual void TickDPI(); - virtual void ToggleInputMode(); static void SettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*); @@ -144,37 +126,10 @@ protected: UPROPERTY() mutable TArray Assets; - UPROPERTY(Config) - bool bCompactMode = false; - - UPROPERTY(Config) - bool bTransparentMode = false; - - UPROPERTY(Config) - float DPIScale = 1.0f; - - UPROPERTY(Config) - bool bShowHelp = true; - - UPROPERTY(Config) - bool bShowWindowsInMainMenu = true; - UPROPERTY(Config) bool bRegisterDefaultCommands = true; - UPROPERTY(Config) - bool bAuthorizeInactiveInput = true; - - UPROPERTY(Config) - bool bAuthorizeExclusiveInput = true; - - UPROPERTY(Config) - bool bAuthorizeSharedInput = true; - - UPROPERTY(Config) - bool bHideMainMenuOnGameInput = true; - - TSharedPtr ImGuiWidget = nullptr; + FCogImguiContext Context; TArray Windows; @@ -184,8 +139,6 @@ protected: TArray SpaceWindows; - FCogWindow_Inputs* InputsWindow = nullptr; - FCogWindow_Settings* SettingsWindow = nullptr; FCogWindow_Layouts* LayoutsWindow = nullptr; @@ -198,7 +151,7 @@ protected: bool bHideAllWindows = false; - bool bRefreshDPIScale = false; + bool IsInitialized = false; TArray ConsoleCommands; }; diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Inputs.h b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Inputs.h index 89b15ca..6f70f09 100644 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Inputs.h +++ b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Inputs.h @@ -1,60 +1 @@ #pragma once - -#include "CoreMinimal.h" -#include "CogWindow.h" -#include "CogWindowConfig.h" -#include "CogWindow_Inputs.generated.h" - -class UCogEngineConfig_Inputs; - -class COGWINDOW_API FCogWindow_Inputs : public FCogWindow -{ - typedef FCogWindow Super; - -public: - - virtual void Initialize() override; - - virtual void PreSaveConfig() override; - -protected: - - virtual void RenderContent() override; - - TObjectPtr Config = nullptr; -}; - -//-------------------------------------------------------------------------------------------------------------------------- -UCLASS(Config = Cog) -class UCogEngineConfig_Inputs : public UCogWindowConfig -{ - GENERATED_BODY() - -public: - - UPROPERTY(Config) - bool bEnableInput = false; - - UPROPERTY(Config) - bool bShareMouse = true; - - UPROPERTY(Config) - bool bNavEnableKeyboard = true; - - UPROPERTY(Config) - bool bNavEnableGamepad = true; - - UPROPERTY(Config) - bool bNavNoCaptureInput = true; - - virtual void Reset() override - { - Super::Reset(); - - bEnableInput = false; - bShareMouse = true; - bNavEnableKeyboard = true; - bNavEnableGamepad = true; - bNavNoCaptureInput = true; - } -}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Settings.h b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Settings.h index adfce0f..b6cf101 100644 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Settings.h +++ b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Settings.h @@ -2,7 +2,12 @@ #include "CoreMinimal.h" #include "CogWindow.h" +#include "CogWindowConfig.h" +#include "CogWindow_Settings.generated.h" +class UCogEngineConfig_Settings; + +//-------------------------------------------------------------------------------------------------------------------------- class COGWINDOW_API FCogWindow_Settings : public FCogWindow { typedef FCogWindow Super; @@ -11,7 +16,83 @@ public: virtual void Initialize() override; + virtual void RenderTick(float DeltaTime) override; + + const UCogWindowConfig_Settings* GetSettingsConfig() const { return Config; } + + void SetDPIScale(float Value); + protected: virtual void RenderContent() override; + + virtual void PreSaveConfig() override; + + virtual void ResetConfig() override; + + TObjectPtr Config = nullptr; }; + + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogWindowConfig_Settings : public UCogWindowConfig +{ + 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 = true; + + UPROPERTY(Config) + bool bNavEnableKeyboard = true; + + UPROPERTY(Config) + bool bNavEnableGamepad = true; + + UPROPERTY(Config) + bool bNavNoCaptureInput = true; + + UPROPERTY(Config) + bool bNoMouseCursorChange = false; + + virtual void Reset() override + { + Super::Reset(); + + DPIScale = 1.0f; + bEnableViewports = false; + bCompactMode = false; + bTransparentMode = false; + bShowHelp = true; + bShowWindowsInMainMenu = true; + bEnableInput = false; + bShareMouse = true; + bNavEnableKeyboard = true; + bNavEnableGamepad = true; + bNavNoCaptureInput = true; + bNoMouseCursorChange = false; + } +}; \ No newline at end of file diff --git a/Source/CogSample/CogSampleGameState.cpp b/Source/CogSample/CogSampleGameState.cpp index 54b34e5..e68584b 100644 --- a/Source/CogSample/CogSampleGameState.cpp +++ b/Source/CogSample/CogSampleGameState.cpp @@ -36,6 +36,7 @@ #include "CogEngineWindow_Scalability.h" #include "CogEngineWindow_Selection.h" #include "CogEngineWindow_Skeleton.h" +#include "CogEngineWindow_Slate.h" #include "CogEngineWindow_Spawns.h" #include "CogEngineWindow_Stats.h" #include "CogEngineWindow_TimeScale.h" @@ -162,6 +163,8 @@ void ACogSampleGameState::InitializeCog() CogWindowManager->AddWindow("Engine.Skeleton"); + CogWindowManager->AddWindow("Engine.Slate"); + CogWindowManager->AddWindow("Engine.Spawns"); FCogEngineWindow_Stats* StatsWindow = CogWindowManager->AddWindow("Engine.Stats");