From 860c9be3492c9abc729ab8996e459899e8586188 Mon Sep 17 00:00:00 2001 From: Arnaud Jamin Date: Sat, 4 Nov 2023 23:20:21 -0400 Subject: [PATCH] CogEngine: Plot window improvements. --- .../Private/CogEngineWindow_Plots.cpp | 926 ++++++++++-------- .../CogEngine/Public/CogEngineWindow_Plots.h | 50 + .../CogWindow/Private/CogWindowWidgets.cpp | 37 + .../CogWindow/Public/CogWindowWidgets.h | 4 + 4 files changed, 592 insertions(+), 425 deletions(-) diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp index 7f61af6..1fe6ea6 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp @@ -7,14 +7,33 @@ #include "implot_internal.h" #include "Kismet/GameplayStatics.h" +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::Initialize() +{ + Super::Initialize(); + + bHasMenu = true; + + Config = GetConfig(); +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Plots::RenderHelp() { ImGui::Text( - "This window plots values overtime. When applicable, only the values of the selected actor are displayed." + "This window plots values and events overtime. When applicable, only the values of the selected actor are displayed." ); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::ResetConfig() +{ + Super::ResetConfig(); + + Config->Reset(); +} + + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Plots::RenderTick(float DeltaTime) { @@ -27,454 +46,511 @@ void FCogEngineWindow_Plots::RenderContent() { Super::RenderContent(); - static int Rows = 1; - static int Cols = 1; + RenderMenu(); if (ImGui::BeginTable("PlotTable", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { - ImGui::TableSetupColumn("Settings", ImGuiTableColumnFlags_WidthFixed, FCogWindowWidgets::GetFontWidth() * 20.0f); - ImGui::TableSetupColumn("Graph", ImGuiTableColumnFlags_WidthStretch, 0.0f); + ImGui::TableSetupColumn("PlotsList", ImGuiTableColumnFlags_WidthFixed, FCogWindowWidgets::GetFontWidth() * 20.0f); + ImGui::TableSetupColumn("Plots", ImGuiTableColumnFlags_WidthStretch, 0.0f); ImGui::TableNextRow(); - //-------------------------------------------------------------------------------------- - // Settings and Entries - //-------------------------------------------------------------------------------------- + TArray VisiblePlots; + ImGui::TableNextColumn(); - if (ImGui::Button("Settings")) - { - ImGui::OpenPopup("SettingsPopup"); - } - ImGui::SameLine(); - FCogWindowWidgets::ToggleButton(&FCogDebugPlot::Pause, "Pause", "Pause", ImVec4(1.0f, 0.0f, 0.0f, 1.0f), ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); + RenderPlotsList(VisiblePlots); - if (ImGui::BeginPopup("SettingsPopup")) - { - ImGui::SliderInt("Rows", &Rows, 1, 5); + ImGui::TableNextColumn(); + RenderPlots(VisiblePlots); - if (ImGui::Button("Clear Data", ImVec2(-1, 0))) + ImGui::EndTable(); + } + + bApplyTimeScale = false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderMenu() +{ + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Options")) + { + if (ImGui::MenuItem("Clear Data")) { FCogDebugPlot::Clear(); } - if (ImGui::Button("Reset Layout", ImVec2(-1, 0))) + if (ImGui::MenuItem("Reset")) { FCogDebugPlot::Pause = false; - Rows = 1; - FCogDebugPlot::Reset(); + ResetConfig(); } - ImGui::EndPopup(); - } - - if (ImGui::BeginChild("Separator", ImVec2(0, 2))) - { ImGui::Separator(); - } - ImGui::EndChild(); - TArray VisiblePlots; - if (ImGui::BeginChild("Plots", ImVec2(0, -1))) - { - int Index = 0; - - for (FCogDebugPlotEntry& Plot : FCogDebugPlot::Plots) + FCogWindowWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderInt("Rows", &Config->Rows, 1, 5)) { - const auto Label = StringCast(*Plot.Name.ToString()); - - if (Plot.CurrentYAxis != ImAxis_COUNT && Plot.CurrentRow != INDEX_NONE) - { - VisiblePlots.Add(&Plot); - } - - ImGui::PushID(Index); - - ImGui::PushStyleColor(ImGuiCol_Text, Plot.IsEventPlot ? IM_COL32(128, 128, 255, 255) : IM_COL32(255, 255, 255, 255)); - ImGui::Selectable(Label.Get(), false, 0); - ImGui::PopStyleColor(); - - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - ImGui::SetDragDropPayload("DragAndDrop", Label.Get(), Label.Length() + 1); - ImGui::TextUnformatted(Label.Get()); - ImGui::EndDragDropSource(); - } - - ImGui::PopID(); - Index++; + bApplyTimeScale = true; } - } - ImGui::EndChild(); - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) + + FCogWindowWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderFloat("Time Range", &Config->TimeRange, 1.0f, 30.0f, "%0.1f")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) - { - Plot->ResetAxis(); - } + bApplyTimeScale = true; } - ImGui::EndDragDropTarget(); + + ImGui::EndMenu(); } - //-------------------------------------------------------------------------------------- - // Graph - //-------------------------------------------------------------------------------------- - ImGui::TableNextColumn(); - if (ImGui::BeginChild("Graph", ImVec2(0, -1))) - { - static float RowRatios[] = { 1, 1, 1, 1, 1, 1 }; - static float ColRatios[] = { 1 }; - static ImPlotSubplotFlags SubplotsFlags = ImPlotSubplotFlags_LinkCols; - if (ImPlot::BeginSubplots("", Rows, Cols, ImVec2(-1, -1), SubplotsFlags, RowRatios, ColRatios)) - { - for (int PlotIndex = 0; PlotIndex < Rows; ++PlotIndex) - { - if (ImPlot::BeginPlot("##Plot", ImVec2(-1, 250))) - { - ImPlotAxisFlags HasPlotOnAxisY1 = false; - ImPlotAxisFlags HasPlotOnAxisY2 = false; - ImPlotAxisFlags HasPlotOnAxisY3 = false; + FCogWindowWidgets::ToggleMenuButton(&FCogDebugPlot::Pause, "Pause", ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); - for (FCogDebugPlotEntry* PlotPtr : VisiblePlots) - { - HasPlotOnAxisY1 |= PlotPtr->CurrentYAxis == ImAxis_Y1 && PlotPtr->CurrentRow == PlotIndex; - HasPlotOnAxisY2 |= PlotPtr->CurrentYAxis == ImAxis_Y2 && PlotPtr->CurrentRow == PlotIndex; - HasPlotOnAxisY3 |= PlotPtr->CurrentYAxis == ImAxis_Y3 && PlotPtr->CurrentRow == PlotIndex; - } - - ImPlot::SetupAxis(ImAxis_X1, NULL, ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines); - ImPlot::SetupAxis(ImAxis_Y1, HasPlotOnAxisY1 ? "" : "[drop here]", (HasPlotOnAxisY1 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit); - ImPlot::SetupAxis(ImAxis_Y2, HasPlotOnAxisY2 ? "" : "[drop here]", (HasPlotOnAxisY2 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); - ImPlot::SetupAxis(ImAxis_Y3, HasPlotOnAxisY3 ? "" : "[drop here]", (HasPlotOnAxisY3 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); - - //-------------------------------------------------------------------------------------------------- - // Set the initial X axis range. After it is automatically updated to move with the current time. - //-------------------------------------------------------------------------------------------------- - ImPlot::SetupAxisLimits(ImAxis_X1, 0, 10.0f, ImGuiCond_Appearing); - - const ImPlotRange& Range = ImPlot::GetCurrentPlot()->Axes[ImAxis_X1].Range; - const float AxisXRange = Range.Max - Range.Min; - - //---------------------------------------------------------------- - // Draw a vertical lines representing the current time and the mouse time - //---------------------------------------------------------------- - ImDrawList* PlotDrawList = ImPlot::GetPlotDrawList(); - const ImVec2 PlotMin = ImPlot::GetPlotPos(); - const ImVec2 PlotSize = ImPlot::GetPlotSize(); - const ImVec2 PlotMax = ImVec2(PlotMin.x + PlotSize.x, PlotMin.y + PlotSize.y); - const float PlotTop = PlotMin.y; - const float TimeBarBottom = PlotTop + PlotSize.y; - - ImPlot::PushPlotClipRect(); - PlotDrawList->AddLine(ImVec2(ImGui::GetMousePos().x, PlotTop), ImVec2(ImGui::GetMousePos().x, TimeBarBottom), IM_COL32(128, 128, 128, 64)); - if (FCogDebugPlot::Pause) - { - const float Time = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0; - const float TimeBarX = ImPlot::PlotToPixels(Time, 0.0f).x; - PlotDrawList->AddLine(ImVec2(TimeBarX, PlotTop), ImVec2(TimeBarX, TimeBarBottom), IM_COL32(255, 255, 255, 64)); - } - ImPlot::PopPlotClipRect(); - - for (FCogDebugPlotEntry* PlotPtr : VisiblePlots) - { - if (PlotPtr == nullptr) - { - continue; - } - - FCogDebugPlotEntry& Entry = *PlotPtr; - - if (Entry.CurrentRow == PlotIndex) - { - //-------------------------------------------------------------------------------- - // Make the time axis move forward automatically, unless the user pauses or zoom. - //-------------------------------------------------------------------------------- - if (FCogDebugPlot::Pause == false && ImGui::GetIO().MouseWheel == 0) - { - ImPlot::SetupAxisLimits(ImAxis_X1, Entry.Time - AxisXRange, Entry.Time, ImGuiCond_Always); - } - - ImPlot::SetAxis(Entry.CurrentYAxis); - - ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL); - const auto Label = StringCast(*Entry.Name.ToString()); - - //---------------------------------------------------------------- - // Pause the scrolling if the user drag inside - //---------------------------------------------------------------- - ImVec2 Mouse = ImGui::GetMousePos(); - if (Mouse.x > PlotMin.x - && Mouse.y > PlotMin.y - && Mouse.x < PlotMax.x - && Mouse.y < PlotMax.y - && ImGui::GetDragDropPayload() == nullptr) - { - ImVec2 Drag = ImGui::GetMouseDragDelta(0); - if (FMath::Abs(Drag.x) > 10) - { - FCogDebugPlot::Pause = true; - } - } - - //------------------------------------------------------- - // Plot Events - //------------------------------------------------------- - const bool IsEventPlot = Entry.Events.Num() > 0; - if (IsEventPlot) - { - //-------------------------------------------------------------------- - // Update plot time for events as events are not pushed every frames - //-------------------------------------------------------------------- - Entry.UpdateTime(GetWorld()); - - ImPlot::SetupAxisLimits(Entry.CurrentYAxis, 0, Entry.MaxRow + 2, ImGuiCond_Always); - - ImPlot::PushPlotClipRect(); - - - //---------------------------------------------------------------- - // Plot line only to make the plotter move in time and auto scale - //---------------------------------------------------------------- - ImVector DummyData; - DummyData.push_back(ImVec2(0, 0)); - DummyData.push_back(ImVec2(0, 8)); - ImPlot::PlotLine(Label.Get(), &DummyData[0].x, &DummyData[0].y, DummyData.size(), Entry.ValueOffset, 2 * sizeof(float)); - - const FCogDebugPlotEvent* HoveredEvent = nullptr; - - for (const FCogDebugPlotEvent& Event : Entry.Events) - { - const ImVec2 PosBot = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.8f)); - const ImVec2 PosTop = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.2f)); - const ImVec2 PosMid(PosBot.x, PosBot.y + (PosTop.y - PosBot.y) * 0.5f); - - const bool IsInstant = Event.StartTime == Event.EndTime; - if (IsInstant) - { - const float Radius = 10.0f; - PlotDrawList->AddNgon(PosMid, 10, Event.BorderColor, 4); - PlotDrawList->AddNgonFilled(PosMid, 10, Event.FillColor, 4); - PlotDrawList->AddText(ImVec2(PosMid.x + 15, PosMid.y - 6), IM_COL32(255, 255, 255, 255), TCHAR_TO_ANSI(*Event.DisplayName)); - - if ((Mouse.x > PosMid.x - Radius) && (Mouse.x < PosMid.x + Radius) && (Mouse.y > PosMid.y - Radius) && (Mouse.y < PosMid.y + Radius)) - { - HoveredEvent = &Event; - } - } - else - { - const float ActualEndTime = Event.GetActualEndTime(Entry); - const ImVec2 PosEnd = ImPlot::PlotToPixels(ImPlotPoint(ActualEndTime, 0)); - const ImVec2 Min = ImVec2(PosBot.x, PosBot.y); - const ImVec2 Max = ImVec2(PosEnd.x, PosTop.y); - - ImDrawFlags Flags = Event.EndTime == 0.0f ? ImDrawFlags_RoundCornersLeft : ImDrawFlags_RoundCornersAll; - PlotDrawList->AddRect(Min, Max, Event.BorderColor, 6.0f, Flags); - PlotDrawList->AddRectFilled(Min, Max, Event.FillColor, 6.0f, Flags); - PlotDrawList->PushClipRect(ImMax(Min, PlotMin), ImMin(Max, PlotMax)); - PlotDrawList->AddText(ImVec2(PosMid.x + 5, PosMid.y - 7), IM_COL32(255, 255, 255, 255), TCHAR_TO_ANSI(*Event.DisplayName)); - PlotDrawList->PopClipRect(); - - if (Mouse.x > Min.x && Mouse.x < Max.x && Mouse.y > Min.y && Mouse.y < Max.y) - { - HoveredEvent = &Event; - } - } - - - } - - //------------------------------------------------------- - // Write info on the graph to help debugging - //------------------------------------------------------- - //char Buffer[64]; - //ImFormatString(Buffer, 64, "%0.1f %0.1f", Mouse.x, Mouse.y); - //PlotDrawList->AddText(ImVec2(PlotMin.x + 50, PlotMin.y + 100), IM_COL32(255, 255, 255, 255), Buffer); - - //------------------------------------------------------- - // Hovered event tooltip - //------------------------------------------------------- - if (ImPlot::IsPlotHovered() && HoveredEvent != nullptr) - { - FCogWindowWidgets::BeginTableTooltip(); - if (ImGui::BeginTable("Params", 2, ImGuiTableFlags_Borders)) - { - //------------------------ - // Event Name - //------------------------ - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Name"); - ImGui::TableNextColumn(); - ImGui::Text("%s", TCHAR_TO_ANSI(*HoveredEvent->DisplayName)); - - //------------------------ - // Owner Name - //------------------------ - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Owner"); - ImGui::TableNextColumn(); - ImGui::Text("%s", TCHAR_TO_ANSI(*HoveredEvent->OwnerName)); - - //------------------------ - // Times - //------------------------ - if (HoveredEvent->EndTime != HoveredEvent->StartTime) - { - const float ActualEndTime = HoveredEvent->GetActualEndTime(Entry); - const uint64 ActualEndFrame = HoveredEvent->GetActualEndFrame(Entry); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Duration"); - ImGui::TableNextColumn(); - ImGui::Text("%0.2fs", ActualEndTime - HoveredEvent->StartTime); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Frames"); - ImGui::TableNextColumn(); - ImGui::Text("%d [%d-%d]", - (int32)(ActualEndFrame - HoveredEvent->StartFrame), - (int32)(HoveredEvent->StartFrame % 1000), - (int32)(ActualEndFrame % 1000)); - } - else - { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Frame"); - ImGui::TableNextColumn(); - ImGui::Text("%d", (int32)(HoveredEvent->StartFrame % 1000)); - } - - //------------------------ - // Params - //------------------------ - for (FCogDebugPlotEventParams Param : HoveredEvent->Params) - { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s", TCHAR_TO_ANSI(*Param.Name.ToString())); - ImGui::TableNextColumn(); - ImGui::Text("%s", TCHAR_TO_ANSI(*Param.Value)); - } - ImGui::EndTable(); - } - FCogWindowWidgets::EndTableTooltip(); - } - - ImPlot::PopPlotClipRect(); - } - //------------------------------------------------------- - // Plot Values - //------------------------------------------------------- - else - { - //---------------------------------------------------------------- - // Custom tooltip - //---------------------------------------------------------------- - if (ImPlot::IsPlotHovered()) - { - float Value; - if (Entry.FindValue(ImPlot::GetPlotMousePos().x, Value)) - { - ImGui::BeginTooltip(); - ImGui::Text("%s: %0.2f", Label.Get(), Value); - ImGui::EndTooltip(); - } - } - - if (Entry.ShowValuesMarkers) - { - ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); - } - - ImPlot::PlotLine(Label.Get(), &Entry.Values[0].x, &Entry.Values[0].y, Entry.Values.size(), ImPlotLineFlags_None, Entry.ValueOffset, 2 * sizeof(float)); - - if (ImPlot::BeginLegendPopup(Label.Get())) - { - if (ImGui::Button("Clear")) - { - Entry.Clear(); - } - ImGui::Checkbox("Show Markers", &Entry.ShowValuesMarkers); - ImPlot::EndLegendPopup(); - } - } - - //------------------------------------------------------- - // Allow legend item labels to be drag and drop sources - //------------------------------------------------------- - if (ImPlot::BeginDragDropSourceItem(Label.Get())) - { - ImGui::SetDragDropPayload("DragAndDrop", Label.Get(), Label.Length() + 1); - ImGui::TextUnformatted(Label.Get()); - ImPlot::EndDragDropSource(); - } - } - - } - - //------------------------------------------------------- - // Allow the main plot area to be a drag and drop target - //------------------------------------------------------- - if (ImPlot::BeginDragDropTargetPlot()) - { - if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) - { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) - { - Plot->AssignAxis(PlotIndex, ImAxis_Y1); - } - } - ImPlot::EndDragDropTarget(); - } - - //------------------------------------------------------- - // Allow each y-axis to be a drag and drop target - //------------------------------------------------------- - for (int y = ImAxis_Y1; y <= ImAxis_Y3; ++y) - { - if (ImPlot::BeginDragDropTargetAxis(y)) - { - if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) - { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) - { - Plot->AssignAxis(PlotIndex, y); - } - } - ImPlot::EndDragDropTarget(); - } - } - - //------------------------------------------------------- - // Allow the legend to be a drag and drop target - //------------------------------------------------------- - if (ImPlot::BeginDragDropTargetLegend()) - { - if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) - { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) - { - Plot->AssignAxis(PlotIndex, ImAxis_Y1); - } - } - ImPlot::EndDragDropTarget(); - } - - ImPlot::EndPlot(); - } - } - ImPlot::EndSubplots(); - } - } - ImGui::EndChild(); - - ImGui::EndTable(); + ImGui::EndMenuBar(); } -} \ No newline at end of file +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderPlots(const TArray& VisiblePlots) +{ + if (ImGui::BeginChild("Graph", ImVec2(0, -1))) + { + static float RowRatios[] = { 1, 1, 1, 1, 1, 1 }; + static float ColRatios[] = { 1 }; + static ImPlotSubplotFlags SubplotsFlags = ImPlotSubplotFlags_LinkCols; + if (ImPlot::BeginSubplots("", Config->Rows, 1, ImVec2(-1, -1), SubplotsFlags, RowRatios, ColRatios)) + { + for (int PlotIndex = 0; PlotIndex < Config->Rows; ++PlotIndex) + { + if (ImPlot::BeginPlot("##Plot", ImVec2(-1, 250))) + { + ImPlotAxisFlags HasPlotOnAxisY1 = false; + ImPlotAxisFlags HasPlotOnAxisY2 = false; + ImPlotAxisFlags HasPlotOnAxisY3 = false; + + for (FCogDebugPlotEntry* PlotPtr : VisiblePlots) + { + HasPlotOnAxisY1 |= PlotPtr->CurrentYAxis == ImAxis_Y1 && PlotPtr->CurrentRow == PlotIndex; + HasPlotOnAxisY2 |= PlotPtr->CurrentYAxis == ImAxis_Y2 && PlotPtr->CurrentRow == PlotIndex; + HasPlotOnAxisY3 |= PlotPtr->CurrentYAxis == ImAxis_Y3 && PlotPtr->CurrentRow == PlotIndex; + } + + ImPlot::SetupAxis(ImAxis_X1, NULL, ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines); + ImPlot::SetupAxis(ImAxis_Y1, HasPlotOnAxisY1 ? "" : "[drop here]", (HasPlotOnAxisY1 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit); + ImPlot::SetupAxis(ImAxis_Y2, HasPlotOnAxisY2 ? "" : "[drop here]", (HasPlotOnAxisY2 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); + ImPlot::SetupAxis(ImAxis_Y3, HasPlotOnAxisY3 ? "" : "[drop here]", (HasPlotOnAxisY3 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); + + //-------------------------------------------------------------------------------------------------- + // Set the initial X axis range. After, it is automatically updated to move with the current time. + //-------------------------------------------------------------------------------------------------- + ImPlot::SetupAxisLimits(ImAxis_X1, 0, Config->TimeRange, ImGuiCond_Appearing); + + const ImPlotRange& PlotRange = ImPlot::GetCurrentPlot()->Axes[ImAxis_X1].Range; + const float TimeRange = PlotRange.Max - PlotRange.Min; + + if (bApplyTimeScale == false) + { + Config->TimeRange = TimeRange; + } + + const ImVec2 PlotMin = ImPlot::GetPlotPos(); + const ImVec2 PlotSize = ImPlot::GetPlotSize(); + const ImVec2 PlotMax = PlotMin + PlotSize; + + //---------------------------------------------------------------- + // Draw a vertical lines representing the current time and the mouse time + //---------------------------------------------------------------- + RenderTimeMarker(); + + for (FCogDebugPlotEntry* PlotPtr : VisiblePlots) + { + if (PlotPtr == nullptr) + { + continue; + } + + FCogDebugPlotEntry& Entry = *PlotPtr; + + if (Entry.CurrentRow == PlotIndex) + { + //-------------------------------------------------------------------------------- + // Make the time axis move forward automatically, unless the user pauses or zoom. + //-------------------------------------------------------------------------------- + if (FCogDebugPlot::Pause == false && ImGui::GetIO().MouseWheel == 0) + { + ImPlot::SetupAxisLimits(ImAxis_X1, Entry.Time - TimeRange, Entry.Time, ImGuiCond_Always); + } + + if (bApplyTimeScale) + { + ImPlot::SetupAxisLimits(ImAxis_X1, Entry.Time - Config->TimeRange, Entry.Time, ImGuiCond_Always); + } + + ImPlot::SetAxis(Entry.CurrentYAxis); + + ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL); + const char* Label = TCHAR_TO_ANSI(*Entry.Name.ToString()); + + //---------------------------------------------------------------- + // Pause the scrolling if the user drag inside + //---------------------------------------------------------------- + const ImVec2 Mouse = ImGui::GetMousePos(); + if (Mouse.x > PlotMin.x + && Mouse.y > PlotMin.y + && Mouse.x < PlotMax.x + && Mouse.y < PlotMax.y + && ImGui::GetDragDropPayload() == nullptr) + { + ImVec2 Drag = ImGui::GetMouseDragDelta(0); + if (FMath::Abs(Drag.x) > 10) + { + FCogDebugPlot::Pause = true; + } + } + + //------------------------------------------------------- + // Plot Events + //------------------------------------------------------- + const bool IsEventPlot = Entry.Events.Num() > 0; + if (IsEventPlot) + { + RenderEvents(Entry, Label, PlotMin, PlotMax); + } + //------------------------------------------------------- + // Plot Values + //------------------------------------------------------- + else + { + RenderValues(Entry, Label); + } + + //------------------------------------------------------- + // Allow legend item labels to be drag and drop sources + //------------------------------------------------------- + if (ImPlot::BeginDragDropSourceItem(Label)) + { + const auto EntryName = StringCast(*Entry.Name.ToString()); + ImGui::SetDragDropPayload("DragAndDrop", EntryName.Get(), EntryName.Length() + 1); + ImGui::TextUnformatted(EntryName.Get()); + ImPlot::EndDragDropSource(); + } + } + + } + + //------------------------------------------------------- + // Allow the main plot area to be a drag and drop target + //------------------------------------------------------- + if (ImPlot::BeginDragDropTargetPlot()) + { + if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) + { + if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) + { + Plot->AssignAxis(PlotIndex, ImAxis_Y1); + } + } + ImPlot::EndDragDropTarget(); + } + + //------------------------------------------------------- + // Allow each y-axis to be a drag and drop target + //------------------------------------------------------- + for (int y = ImAxis_Y1; y <= ImAxis_Y3; ++y) + { + if (ImPlot::BeginDragDropTargetAxis(y)) + { + if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) + { + if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) + { + Plot->AssignAxis(PlotIndex, y); + } + } + ImPlot::EndDragDropTarget(); + } + } + + //------------------------------------------------------- + // Allow the legend to be a drag and drop target + //------------------------------------------------------- + if (ImPlot::BeginDragDropTargetLegend()) + { + if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) + { + if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) + { + Plot->AssignAxis(PlotIndex, ImAxis_Y1); + } + } + ImPlot::EndDragDropTarget(); + } + + ImPlot::EndPlot(); + } + } + ImPlot::EndSubplots(); + } + } + ImGui::EndChild(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderPlotsList(TArray& VisiblePlots) +{ + if (ImGui::BeginChild("Plots", ImVec2(0, -1))) + { + int Index = 0; + + for (FCogDebugPlotEntry& Plot : FCogDebugPlot::Plots) + { + const auto Label = StringCast(*Plot.Name.ToString()); + + if (Plot.CurrentYAxis != ImAxis_COUNT && Plot.CurrentRow != INDEX_NONE) + { + VisiblePlots.Add(&Plot); + } + + ImGui::PushID(Index); + + ImGui::PushStyleColor(ImGuiCol_Text, Plot.IsEventPlot ? IM_COL32(128, 128, 255, 255) : IM_COL32(255, 255, 255, 255)); + ImGui::Selectable(Label.Get(), false, 0); + ImGui::PopStyleColor(); + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload("DragAndDrop", Label.Get(), Label.Length() + 1); + ImGui::TextUnformatted(Label.Get()); + ImGui::EndDragDropSource(); + } + + ImGui::PopID(); + Index++; + } + } + ImGui::EndChild(); + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) + { + if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindPlot(FName((const char*)Payload->Data))) + { + Plot->ResetAxis(); + } + } + ImGui::EndDragDropTarget(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderTimeMarker() +{ + const ImVec2 PlotMin = ImPlot::GetPlotPos(); + const ImVec2 PlotSize = ImPlot::GetPlotSize(); + ImDrawList* PlotDrawList = ImPlot::GetPlotDrawList(); + + const float PlotTop = PlotMin.y; + const float TimeBarBottom = PlotTop + PlotSize.y; + + ImPlot::PushPlotClipRect(); + PlotDrawList->AddLine(ImVec2(ImGui::GetMousePos().x, PlotTop), ImVec2(ImGui::GetMousePos().x, TimeBarBottom), IM_COL32(128, 128, 128, 64)); + if (FCogDebugPlot::Pause) + { + const float Time = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0; + const float TimeBarX = ImPlot::PlotToPixels(Time, 0.0f).x; + PlotDrawList->AddLine(ImVec2(TimeBarX, PlotTop), ImVec2(TimeBarX, TimeBarBottom), IM_COL32(255, 255, 255, 64)); + } + ImPlot::PopPlotClipRect(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderValues(FCogDebugPlotEntry& Entry, const char* Label) +{ + //---------------------------------------------------------------- + // Custom tooltip + //---------------------------------------------------------------- + if (ImPlot::IsPlotHovered()) + { + float Value; + if (Entry.FindValue(ImPlot::GetPlotMousePos().x, Value)) + { + ImGui::BeginTooltip(); + ImGui::Text("%s: %0.2f", Label, Value); + ImGui::EndTooltip(); + } + } + + if (Entry.ShowValuesMarkers) + { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + } + + ImPlot::PlotLine(Label, &Entry.Values[0].x, &Entry.Values[0].y, Entry.Values.size(), ImPlotLineFlags_None, Entry.ValueOffset, 2 * sizeof(float)); + + if (ImPlot::BeginLegendPopup(Label)) + { + if (ImGui::Button("Clear")) + { + Entry.Clear(); + } + ImGui::Checkbox("Show Markers", &Entry.ShowValuesMarkers); + ImPlot::EndLegendPopup(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax) +{ + const ImVec2 Mouse = ImGui::GetMousePos(); + ImDrawList* PlotDrawList = ImPlot::GetPlotDrawList(); + + //-------------------------------------------------------------------- + // Update plot time for events as events are not pushed every frames + //-------------------------------------------------------------------- + Entry.UpdateTime(GetWorld()); + + ImPlot::SetupAxisLimits(Entry.CurrentYAxis, 0, Entry.MaxRow + 2, ImGuiCond_Always); + + ImPlot::PushPlotClipRect(); + + + //---------------------------------------------------------------- + // Plot line only to make the plotter move in time and auto scale + //---------------------------------------------------------------- + ImVector DummyData; + DummyData.push_back(ImVec2(0, 0)); + DummyData.push_back(ImVec2(0, 8)); + ImPlot::PlotLine(Label, &DummyData[0].x, &DummyData[0].y, DummyData.size(), Entry.ValueOffset, 2 * sizeof(float)); + + const FCogDebugPlotEvent* HoveredEvent = nullptr; + + for (const FCogDebugPlotEvent& Event : Entry.Events) + { + const ImVec2 PosBot = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.8f)); + const ImVec2 PosTop = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.2f)); + const ImVec2 PosMid(PosBot.x, PosBot.y + (PosTop.y - PosBot.y) * 0.5f); + + const bool IsInstant = Event.StartTime == Event.EndTime; + if (IsInstant) + { + const float Radius = 10.0f; + PlotDrawList->AddNgon(PosMid, 10, Event.BorderColor, 4); + PlotDrawList->AddNgonFilled(PosMid, 10, Event.FillColor, 4); + PlotDrawList->AddText(ImVec2(PosMid.x + 15, PosMid.y - 6), IM_COL32(255, 255, 255, 255), TCHAR_TO_ANSI(*Event.DisplayName)); + + if ((Mouse.x > PosMid.x - Radius) && (Mouse.x < PosMid.x + Radius) && (Mouse.y > PosMid.y - Radius) && (Mouse.y < PosMid.y + Radius)) + { + HoveredEvent = &Event; + } + } + else + { + const float ActualEndTime = Event.GetActualEndTime(Entry); + const ImVec2 PosEnd = ImPlot::PlotToPixels(ImPlotPoint(ActualEndTime, 0)); + const ImVec2 Min = ImVec2(PosBot.x, PosBot.y); + const ImVec2 Max = ImVec2(PosEnd.x, PosTop.y); + + ImDrawFlags Flags = Event.EndTime == 0.0f ? ImDrawFlags_RoundCornersLeft : ImDrawFlags_RoundCornersAll; + PlotDrawList->AddRect(Min, Max, Event.BorderColor, 6.0f, Flags); + PlotDrawList->AddRectFilled(Min, Max, Event.FillColor, 6.0f, Flags); + PlotDrawList->PushClipRect(ImMax(Min, PlotMin), ImMin(Max, PlotMax)); + PlotDrawList->AddText(ImVec2(PosMid.x + 5, PosMid.y - 7), IM_COL32(255, 255, 255, 255), TCHAR_TO_ANSI(*Event.DisplayName)); + PlotDrawList->PopClipRect(); + + if (Mouse.x > Min.x && Mouse.x < Max.x && Mouse.y > Min.y && Mouse.y < Max.y) + { + HoveredEvent = &Event; + } + } + } + + //------------------------------------------------------- + // Write info on the graph to help debugging + //------------------------------------------------------- + //char Buffer[64]; + //ImFormatString(Buffer, 64, "%0.1f %0.1f", Mouse.x, Mouse.y); + //PlotDrawList->AddText(ImVec2(PlotMin.x + 50, PlotMin.y + 100), IM_COL32(255, 255, 255, 255), Buffer); + + //------------------------------------------------------- + // Hovered event tooltip + //------------------------------------------------------- + RenderEventTooltip(HoveredEvent, Entry); + + ImPlot::PopPlotClipRect(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, FCogDebugPlotEntry& Entry) +{ + if (ImPlot::IsPlotHovered() && HoveredEvent != nullptr) + { + FCogWindowWidgets::BeginTableTooltip(); + if (ImGui::BeginTable("Params", 2, ImGuiTableFlags_Borders)) + { + //------------------------ + // Event Name + //------------------------ + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::Text("%s", TCHAR_TO_ANSI(*HoveredEvent->DisplayName)); + + //------------------------ + // Owner Name + //------------------------ + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Owner"); + ImGui::TableNextColumn(); + ImGui::Text("%s", TCHAR_TO_ANSI(*HoveredEvent->OwnerName)); + + //------------------------ + // Times + //------------------------ + if (HoveredEvent->EndTime != HoveredEvent->StartTime) + { + const float ActualEndTime = HoveredEvent->GetActualEndTime(Entry); + const uint64 ActualEndFrame = HoveredEvent->GetActualEndFrame(Entry); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Duration"); + ImGui::TableNextColumn(); + ImGui::Text("%0.2fs", ActualEndTime - HoveredEvent->StartTime); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Frames"); + ImGui::TableNextColumn(); + ImGui::Text("%d [%d-%d]", + (int32)(ActualEndFrame - HoveredEvent->StartFrame), + (int32)(HoveredEvent->StartFrame % 1000), + (int32)(ActualEndFrame % 1000)); + } + else + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Frame"); + ImGui::TableNextColumn(); + ImGui::Text("%d", (int32)(HoveredEvent->StartFrame % 1000)); + } + + //------------------------ + // Params + //------------------------ + for (FCogDebugPlotEventParams Param : HoveredEvent->Params) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", TCHAR_TO_ANSI(*Param.Name.ToString())); + ImGui::TableNextColumn(); + ImGui::Text("%s", TCHAR_TO_ANSI(*Param.Value)); + } + ImGui::EndTable(); + } + FCogWindowWidgets::EndTableTooltip(); + } +} diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h index 63df402..2bac407 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h @@ -2,6 +2,13 @@ #include "CoreMinimal.h" #include "CogWindow.h" +#include "CogWindowConfig.h" +#include "CogEngineWindow_Plots.generated.h" + +struct ImVec2; +struct FCogDebugPlotEvent; +struct FCogDebugPlotEntry; +class UCogEngineConfig_Plots; class COGENGINE_API FCogEngineWindow_Plots : public FCogWindow { @@ -9,12 +16,55 @@ class COGENGINE_API FCogEngineWindow_Plots : public FCogWindow protected: + virtual void Initialize() override; + virtual void RenderHelp() override; + virtual void ResetConfig() override; + virtual void RenderTick(float DeltaTime) override; virtual void RenderContent() override; + void RenderPlotsList(TArray& VisiblePlots); + + void RenderPlots(const TArray& VisiblePlots); + + void RenderMenu(); + + void RenderTimeMarker(); + + void RenderValues(FCogDebugPlotEntry& Entry, const char* Label); + + void RenderEvents(FCogDebugPlotEntry& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax); + + void RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, FCogDebugPlotEntry& Entry); + + TObjectPtr Config = nullptr; + + bool bApplyTimeScale = false; + private: }; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineConfig_Plots : public UCogWindowConfig +{ + GENERATED_BODY() + +public: + + UPROPERTY(Config) + int Rows = 1; + + UPROPERTY(Config) + float TimeRange = 10.0f; + + virtual void Reset() override + { + Rows = 1; + TimeRange = 10.0f; + } +}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindowWidgets.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindowWidgets.cpp index 85c51e4..e820c7f 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindowWidgets.cpp +++ b/Plugins/Cog/Source/CogWindow/Private/CogWindowWidgets.cpp @@ -72,6 +72,43 @@ void FCogWindowWidgets::ProgressBarCentered(float Fraction, const ImVec2& Size, } } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindowWidgets::ToggleMenuButton(bool* Value, const char* Text, const ImVec4& TrueColor) +{ + bool IsTrue = *Value; + if (IsTrue) + { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(TrueColor.x, TrueColor.y, TrueColor.z, TrueColor.w * 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(TrueColor.x, TrueColor.y, TrueColor.z, TrueColor.w * 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(TrueColor.x, TrueColor.y, TrueColor.z, TrueColor.w * 1.0f)); + } + else + { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + } + + if (ImGui::Button(Text)) + { + *Value = !*Value; + } + + if (IsTrue) + { + ImGui::PopStyleColor(3); + } + else + { + ImGui::PopStyleColor(1); + } + +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindowWidgets::ToggleButton(bool* Value, const char* Text, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size) +{ + ToggleButton(Value, Text, Text, TrueColor, FalseColor, Size); +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogWindowWidgets::ToggleButton(bool* Value, const char* TextTrue, const char* TextFalse, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size) { diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindowWidgets.h b/Plugins/Cog/Source/CogWindow/Public/CogWindowWidgets.h index 661a7b7..1705c50 100644 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindowWidgets.h +++ b/Plugins/Cog/Source/CogWindow/Public/CogWindowWidgets.h @@ -18,6 +18,10 @@ public: static void ProgressBarCentered(float Fraction, const ImVec2& Size, const char* Overlay); + static void ToggleMenuButton(bool* Value, const char* Text, const ImVec4& TrueColor); + + static void ToggleButton(bool* Value, const char* Text, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size = ImVec2(0, 0)); + static void ToggleButton(bool* Value, const char* TextTrue, const char* TextFalse, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size = ImVec2(0, 0)); static bool MultiChoiceButton(const char* Label, bool IsSelected, const ImVec2& Size = ImVec2(0, 0));