198 lines
No EOL
6.7 KiB
C++
198 lines
No EOL
6.7 KiB
C++
// Copyright 2020 Phyronnaz
|
|
|
|
#include "VoxelMessagesEditor.h"
|
|
#include "VoxelSettings.h"
|
|
#include "VoxelMinimal.h"
|
|
|
|
#include "Logging/MessageLog.h"
|
|
#include "Misc/CoreMisc.h"
|
|
#include "Misc/UObjectToken.h"
|
|
#include "UObject/Stack.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "Engine/BlueprintGeneratedClass.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "Kismet2/KismetDebugUtilities.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Editor.h"
|
|
|
|
inline void AddButton(
|
|
FNotificationInfo& Info,
|
|
const FSimpleDelegate& OnClick,
|
|
const FText& Text,
|
|
const FText& Tooltip,
|
|
bool bCloseOnClick,
|
|
const TSharedRef<TWeakPtr<SNotificationItem>>& PtrToPtr)
|
|
{
|
|
const auto Callback = FSimpleDelegate::CreateLambda([=]()
|
|
{
|
|
OnClick.ExecuteIfBound();
|
|
|
|
if (bCloseOnClick)
|
|
{
|
|
auto Ptr = PtrToPtr->Pin();
|
|
if (Ptr.IsValid())
|
|
{
|
|
Ptr->SetFadeOutDuration(0);
|
|
Ptr->Fadeout();
|
|
}
|
|
}
|
|
});
|
|
|
|
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
|
Text,
|
|
Tooltip,
|
|
Callback,
|
|
SNotificationItem::CS_None));
|
|
}
|
|
|
|
void FVoxelMessagesEditor::LogMessage(const TSharedRef<FTokenizedMessage>& Message, EVoxelShowNotification ShouldShow)
|
|
{
|
|
struct Local
|
|
{
|
|
static void OnMessageLogLinkActivated(const class TSharedRef<IMessageToken>& Token)
|
|
{
|
|
if (Token->GetType() == EMessageToken::Object)
|
|
{
|
|
const TSharedRef<FUObjectToken> UObjectToken = StaticCastSharedRef<FUObjectToken>(Token);
|
|
if (UObjectToken->GetObject().IsValid())
|
|
{
|
|
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(UObjectToken->GetObject().Get());
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const TArray<const FFrame*>& ScriptStack = FBlueprintContextTracker::Get().GetScriptStack();
|
|
|
|
TArray<TSharedPtr<IMessageToken>> ReversedTokens;
|
|
if (ScriptStack.Num() > 0)
|
|
{
|
|
const FFrame& StackFrame = *ScriptStack.Last();
|
|
UClass* ClassContainingCode = FKismetDebugUtilities::FindClassForNode(nullptr, StackFrame.Node);
|
|
UBlueprint* BlueprintObj = (ClassContainingCode ? Cast<UBlueprint>(ClassContainingCode->ClassGeneratedBy) : NULL);
|
|
if (BlueprintObj)
|
|
{
|
|
const int32 BreakpointOffset = StackFrame.Code - StackFrame.Node->Script.GetData() - 1;
|
|
|
|
ReversedTokens.Add(FUObjectToken::Create(BlueprintObj, FText::FromString(BlueprintObj->GetName()))
|
|
->OnMessageTokenActivated(FOnMessageTokenActivated::CreateStatic(&Local::OnMessageLogLinkActivated))
|
|
);
|
|
ReversedTokens.Add(FTextToken::Create(VOXEL_LOCTEXT("Blueprint: ")));
|
|
|
|
// NOTE: StackFrame.Node is not a blueprint node like you may think ("Node" has some legacy meaning)
|
|
ReversedTokens.Add(FUObjectToken::Create(StackFrame.Node, StackFrame.Node->GetDisplayNameText())
|
|
->OnMessageTokenActivated(FOnMessageTokenActivated::CreateStatic(&Local::OnMessageLogLinkActivated))
|
|
);
|
|
ReversedTokens.Add(FTextToken::Create(VOXEL_LOCTEXT("Function: ")));
|
|
|
|
#if WITH_EDITORONLY_DATA // to protect access to GeneratedClass->DebugData
|
|
UBlueprintGeneratedClass* GeneratedClass = Cast<UBlueprintGeneratedClass>(ClassContainingCode);
|
|
if ((GeneratedClass != NULL) && GeneratedClass->DebugData.IsValid())
|
|
{
|
|
UEdGraphNode* BlueprintNode = GeneratedClass->DebugData.FindSourceNodeFromCodeLocation(StackFrame.Node, BreakpointOffset, true);
|
|
// if instead, there is a node we can point to...
|
|
if (BlueprintNode != NULL)
|
|
{
|
|
ReversedTokens.Add(FUObjectToken::Create(BlueprintNode->GetGraph(), FText::FromString(GetNameSafe(BlueprintNode->GetGraph())))
|
|
->OnMessageTokenActivated(FOnMessageTokenActivated::CreateStatic(&Local::OnMessageLogLinkActivated))
|
|
);
|
|
ReversedTokens.Add(FTextToken::Create(VOXEL_LOCTEXT("Graph: ")));
|
|
|
|
ReversedTokens.Add(FUObjectToken::Create(BlueprintNode, BlueprintNode->GetNodeTitle(ENodeTitleType::ListView))
|
|
->OnMessageTokenActivated(FOnMessageTokenActivated::CreateStatic(&Local::OnMessageLogLinkActivated))
|
|
);
|
|
ReversedTokens.Add(FTextToken::Create(VOXEL_LOCTEXT("Node: ")));
|
|
}
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
}
|
|
for (int32 Index = ReversedTokens.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
Message->AddToken(ReversedTokens[Index].ToSharedRef());
|
|
}
|
|
|
|
if (GEditor->PlayWorld || GIsPlayInEditorWorld)
|
|
{
|
|
FMessageLog("PIE").AddMessage(Message);
|
|
}
|
|
else
|
|
{
|
|
FMessageLog("Voxel").AddMessage(Message);
|
|
if (GetDefault<UVoxelSettings>()->bShowNotifications && ShouldShow == EVoxelShowNotification::Show)
|
|
{
|
|
struct FLastNotification
|
|
{
|
|
TWeakPtr<SNotificationItem> Ptr;
|
|
FText Text;
|
|
int32 Count;
|
|
};
|
|
static TArray<FLastNotification> LastNotifications;
|
|
|
|
const FText Text = Message->ToText();
|
|
LastNotifications.RemoveAll([](auto& Notification) { return !Notification.Ptr.IsValid(); });
|
|
|
|
for (auto& LastNotification : LastNotifications)
|
|
{
|
|
auto LastNotificationPtr = LastNotification.Ptr.Pin();
|
|
if (ensure(LastNotificationPtr.IsValid()) && LastNotification.Text.EqualToCaseIgnored(Text))
|
|
{
|
|
LastNotification.Text = Text;
|
|
LastNotification.Count++;
|
|
|
|
LastNotificationPtr->SetText(FText::Format(VOXEL_LOCTEXT("{0} (x{1})"), Text, FText::AsNumber(LastNotification.Count)));
|
|
LastNotificationPtr->ExpireAndFadeout();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
FNotificationInfo Info = FNotificationInfo(Text);
|
|
Info.CheckBoxState = ECheckBoxState::Unchecked;
|
|
Info.ExpireDuration = 10;
|
|
|
|
const TSharedRef<TWeakPtr<SNotificationItem>> PtrToPtr = MakeShared<TWeakPtr<SNotificationItem>>();
|
|
AddButton(Info, {}, VOXEL_LOCTEXT("Close"), VOXEL_LOCTEXT("Close"), true, PtrToPtr);
|
|
const auto Ptr = FSlateNotificationManager::Get().AddNotification(Info);
|
|
*PtrToPtr = Ptr;
|
|
|
|
LastNotifications.Emplace(FLastNotification{ Ptr, Text, 1 });
|
|
}
|
|
}
|
|
}
|
|
|
|
void FVoxelMessagesEditor::ShowNotification(const FVoxelMessages::FNotification& Notification)
|
|
{
|
|
struct FLastNotification
|
|
{
|
|
TWeakPtr<SNotificationItem> Ptr;
|
|
uint64 UniqueId;
|
|
};
|
|
static TArray<FLastNotification> LastNotifications;
|
|
|
|
LastNotifications.RemoveAll([](auto& It) { return !It.Ptr.IsValid(); });
|
|
if (LastNotifications.FindByPredicate([&](auto& It) { return It.UniqueId == Notification.UniqueId; }))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FNotificationInfo Info(FText::FromString(Notification.Message));
|
|
Info.CheckBoxState = ECheckBoxState::Unchecked;
|
|
Info.ExpireDuration = Notification.Duration;
|
|
|
|
const TSharedRef<TWeakPtr<SNotificationItem>> PtrToPtr = MakeShared<TWeakPtr<SNotificationItem>>();
|
|
|
|
for (auto& Button : Notification.Buttons)
|
|
{
|
|
AddButton(Info, Button.OnClick, FText::FromString(Button.Text), FText::FromString(Button.Tooltip), Button.bCloseOnClick, PtrToPtr);
|
|
}
|
|
AddButton(Info, Notification.OnClose, VOXEL_LOCTEXT("Close"), VOXEL_LOCTEXT("Close"), true, PtrToPtr);
|
|
|
|
const auto Ptr = FSlateNotificationManager::Get().AddNotification(Info);
|
|
*PtrToPtr = Ptr;
|
|
|
|
LastNotifications.Add({ Ptr, Notification.UniqueId });
|
|
} |