Skip to content

Instantly share code, notes, and snippets.

@jeremyabel
Created April 17, 2025 21:51
Show Gist options
  • Save jeremyabel/d3ac8054b72466cc531c9cfe203f2453 to your computer and use it in GitHub Desktop.
Save jeremyabel/d3ac8054b72466cc531c9cfe203f2453 to your computer and use it in GitHub Desktop.
Slate Widget Input Debug Panel
// Copyright Feral Cat Den, LLC. All Rights Reserved.
#include "GNInputEventDebugger.h"
#if WITH_GN_DEBUGGER && WITH_SLATE_DEBUGGING
#include "Debugging/SlateDebugging.h"
#include "Development/GNDebugImGuiCommon.h"
#include "UI/GNWidgetStatics.h"
FGNInputEventDebugger::FGNInputEventDebugger()
: bEnabled(false)
, bDebuggerInputEnabled(true)
, MaxInputHistoryEntries(50)
, ProcessInputCounter(0)
, RouteInputCounter(0)
, EnabledInputEvents(false, (uint8)ESlateDebuggingInputEvent::MAX)
{
EnabledInputEvents[(uint8)ESlateDebuggingInputEvent::MouseButtonDown] = true;
EnabledInputEvents[(uint8)ESlateDebuggingInputEvent::MouseButtonUp] = true;
EnabledInputEvents[(uint8)ESlateDebuggingInputEvent::MouseButtonDoubleClick] = true;
EnabledInputEvents[(uint8)ESlateDebuggingInputEvent::KeyDown] = true;
EnabledInputEvents[(uint8)ESlateDebuggingInputEvent::KeyUp] = true;
}
FGNInputEventDebugger::~FGNInputEventDebugger()
{
StopDebugging();
}
void FGNInputEventDebugger::StartDebugging()
{
if (!bEnabled)
{
bEnabled = true;
FSlateDebugging::RegisterWidgetInputRoutingEvent(this);
}
}
void FGNInputEventDebugger::StopDebugging()
{
if (bEnabled)
{
bEnabled = false;
FSlateDebugging::UnregisterWidgetInputRoutingEvent(this);
InputHistoryEntries.Reset();
ProcessInputCounter = 0;
RouteInputCounter = 0;
}
}
TConstArrayView<TSharedRef<FGNInputSlateHistoryEntry>> FGNInputEventDebugger::GetHistoryEntriesView() const
{
return TConstArrayView<TSharedRef<FGNInputSlateHistoryEntry>>(InputHistoryEntries);
}
void FGNInputEventDebugger::OnProcessInput(ESlateDebuggingInputEvent InputEventType, const FInputEvent& Event)
{
if (!bDebuggerInputEnabled && EnabledInputEvents[(uint8)InputEventType])
{
TSharedRef<FGNInputSlateHistoryEntry> NewEntry = MakeShared<FGNInputSlateHistoryEntry>();
NewEntry->EntryType = EGNInputSlateHistoryEntryType::Process;
NewEntry->EventType = InputEventType;
NewEntry->Info += StaticEnum<ESlateDebuggingInputEvent>()->GetDisplayNameTextByValue((int64)InputEventType).ToString();
NewEntry->Info += TEXT(" - ");
NewEntry->Info += Event.ToText().ToString();
NewEntry->bIsVerbose = false;
InputHistoryEntries.Add(NewEntry);
}
++ProcessInputCounter;
}
void FGNInputEventDebugger::OnPreProcessInput(ESlateDebuggingInputEvent InputEventType, const TCHAR* InputPrecessor, bool bHandled)
{
if (!bDebuggerInputEnabled && EnabledInputEvents[(uint8)InputEventType])
{
TSharedRef<FGNInputSlateHistoryEntry> NewEntry = MakeShared<FGNInputSlateHistoryEntry>();
NewEntry->EntryType = EGNInputSlateHistoryEntryType::InputProcessor;
NewEntry->EventType = InputEventType;
NewEntry->Info = bHandled ? TEXT("(Handled)") : TEXT("");
NewEntry->bIsVerbose = !bHandled;
NewEntry->Spaces = ProcessInputCounter + RouteInputCounter;
InputHistoryEntries.Add(NewEntry);
}
}
void FGNInputEventDebugger::OnRouteInput(ESlateDebuggingInputEvent InputEventType, const FName& RoutedType)
{
if (!bDebuggerInputEnabled && EnabledInputEvents[(uint8)InputEventType])
{
TSharedRef<FGNInputSlateHistoryEntry> NewEntry = MakeShared<FGNInputSlateHistoryEntry>();
NewEntry->EntryType = EGNInputSlateHistoryEntryType::Route;
NewEntry->EventType = InputEventType;
NewEntry->Info = RoutedType.ToString();
NewEntry->bIsVerbose = false;
NewEntry->Spaces = ProcessInputCounter + RouteInputCounter;
InputHistoryEntries.Add(NewEntry);
}
++RouteInputCounter;
}
void FGNInputEventDebugger::OnInputEvent(ESlateDebuggingInputEvent InputEventType, const FReply& InReply, const TSharedPtr<SWidget>& HandlerWidget)
{
if (!bDebuggerInputEnabled && EnabledInputEvents[(uint8)InputEventType])
{
TSharedRef<FGNInputSlateHistoryEntry> NewEntry = MakeShared<FGNInputSlateHistoryEntry>();
NewEntry->EntryType = EGNInputSlateHistoryEntryType::Event;
NewEntry->EventType = InputEventType;
NewEntry->bIsVerbose = !InReply.IsEventHandled();
NewEntry->Spaces = ProcessInputCounter + RouteInputCounter;
FString AssetName, WidgetName;
UGNWidgetStatics::GetWidgetDebugInfo(HandlerWidget.Get(), AssetName, WidgetName);
NewEntry->Info += InReply.IsEventHandled() ? TEXT("(Handled) ") : TEXT("");
NewEntry->Info += AssetName;
NewEntry->InfoVerbose = WidgetName;
InputHistoryEntries.Add(NewEntry);
}
}
void FGNInputEventDebugger::OnInputRouted(ESlateDebuggingInputEvent InputEventType)
{
RouteInputCounter = FMath::Max(0, RouteInputCounter - 1);
}
void FGNInputEventDebugger::OnInputProcessed(ESlateDebuggingInputEvent InputEventType)
{
ProcessInputCounter = FMath::Max(0, ProcessInputCounter - 1);
}
namespace InputSlateHistoryEntry
{
using TString = TStringBuilder<64>;
void BuildSpaces(TStringBuilder<64>& Message, int32 Amount)
{
const TCHAR* Space = TEXT(" ");
for (int32 SpaceCounter = 0; SpaceCounter < Amount; ++SpaceCounter)
{
Message.Append(Space);
}
}
}
FString FGNInputSlateHistoryEntry::ToString(bool bVerbose)
{
InputSlateHistoryEntry::TString Message;
InputSlateHistoryEntry::BuildSpaces(Message, Spaces);
switch (EntryType)
{
case EGNInputSlateHistoryEntryType::Event:
Message.Append(TEXT("Event - ")); break;
case EGNInputSlateHistoryEntryType::Process:
Message.Append(TEXT("Process - ")); break;
case EGNInputSlateHistoryEntryType::InputProcessor:
Message.Append(TEXT("InputProcessor - ")); break;
case EGNInputSlateHistoryEntryType::Route:
Message.Append(TEXT("Route - ")); break;
}
Message.Append(Info);
if (bVerbose)
{
Message.Append(TEXT(" - "));
Message.Append(InfoVerbose);
}
return FString(Message);
}
#endif // WITH_GN_DEBUGGER && WITH_SLATE_DEBUGGING
// Copyright Feral Cat Den, LLC. All Rights Reserved.
#pragma once
#if WITH_GN_DEBUGGER && WITH_SLATE_DEBUGGING
#include "CoreMinimal.h"
#include "Debugging/SlateDebugging.h"
#include "Input/Reply.h"
class SWidget;
enum class EGNInputSlateHistoryEntryType : uint8
{
Process,
InputProcessor,
Route,
Event
};
enum class EGNInputSlateHistoryVerbosity : uint8
{
Normal,
Verbose,
VeryVerbose
};
struct FGNInputSlateHistoryEntry
{
EGNInputSlateHistoryEntryType EntryType;
ESlateDebuggingInputEvent EventType;
FString Info;
FString InfoVerbose;
uint8 bIsVerbose : 1;
uint8 Spaces = 0;
FString ToString(bool bVerbose);
};
class FGNInputEventDebugger : public FSlateDebugging::IWidgetInputRoutingEvent
{
public:
FGNInputEventDebugger();
virtual ~FGNInputEventDebugger();
void StartDebugging();
void StopDebugging();
TConstArrayView<TSharedRef<FGNInputSlateHistoryEntry>> GetHistoryEntriesView() const;
bool bEnabled;
bool bDebuggerInputEnabled;
int32 MaxInputHistoryEntries;
protected:
// IWidgetInputRoutingEvent implementation
virtual void OnProcessInput(ESlateDebuggingInputEvent InputEventType, const FInputEvent& Event) override;
virtual void OnPreProcessInput(ESlateDebuggingInputEvent InputEventType, const TCHAR* InputPrecessor, bool bHandled) override;
virtual void OnRouteInput(ESlateDebuggingInputEvent InputEventType, const FName& RoutedType) override;
virtual void OnInputEvent(ESlateDebuggingInputEvent InputEventType, const FReply& InReply, const TSharedPtr<SWidget>& HandlerWidget) override;
virtual void OnInputRouted(ESlateDebuggingInputEvent InputEventType) override;
virtual void OnInputProcessed(ESlateDebuggingInputEvent InputEventType) override;
TArray<TSharedRef<FGNInputSlateHistoryEntry>> InputHistoryEntries;
private:
int32 ProcessInputCounter;
int32 RouteInputCounter;
TBitArray<> EnabledInputEvents;
};
#endif // WITH_GN_DEBUGGER && WITH_SLATE_DEBUGGING
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment