CogEngine: Plot window improvements.

This commit is contained in:
Arnaud Jamin
2023-11-04 23:20:21 -04:00
parent 9d3143298b
commit 860c9be349
4 changed files with 592 additions and 425 deletions
@@ -7,14 +7,33 @@
#include "implot_internal.h"
#include "Kismet/GameplayStatics.h"
//--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_Plots::Initialize()
{
Super::Initialize();
bHasMenu = true;
Config = GetConfig<UCogEngineConfig_Plots>();
}
//--------------------------------------------------------------------------------------------------------------------------
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<FCogDebugPlotEntry*> 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<FCogDebugPlotEntry*> 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<ANSICHAR>(*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<ANSICHAR>(*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<ImVec2> 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();
}
}
}
//--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_Plots::RenderPlots(const TArray<FCogDebugPlotEntry*>& 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<ANSICHAR>(*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<FCogDebugPlotEntry*>& VisiblePlots)
{
if (ImGui::BeginChild("Plots", ImVec2(0, -1)))
{
int Index = 0;
for (FCogDebugPlotEntry& Plot : FCogDebugPlot::Plots)
{
const auto Label = StringCast<ANSICHAR>(*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<ImVec2> 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();
}
}
@@ -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<FCogDebugPlotEntry*>& VisiblePlots);
void RenderPlots(const TArray<FCogDebugPlotEntry*>& 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<UCogEngineConfig_Plots> 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;
}
};
@@ -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)
{
@@ -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));