Files
ProjectEleri/Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditorToolkit.cpp

1591 lines
43 KiB
C++

// Copyright Steve Streeting 2022
// Released under the MIT license https://opensource.org/license/MIT/
#include "SUDSEditorToolkit.h"
#include "EditorReimportHandler.h"
#include "IDetailRootObjectCustomization.h"
#include "IDocumentation.h"
#include "SUDSDialogue.h"
#include "SUDSEditorScriptTools.h"
#include "SUDSEditorVoiceOverTools.h"
#include "SUDSLibrary.h"
#include "SUDSMessageLogger.h"
#include "SUDSScript.h"
#include "SUDSScriptNodeText.h"
#include "SUDSSubsystem.h"
#include "Framework/Text/SlateTextRun.h"
#include "Misc/MessageDialog.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SMultiLineEditableTextBox.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Views/SListView.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "PropertyEditorModule.h"
const FName NAME_SpeakerLine("SpeakerLine");
const FName NAME_Choice("Choice");
const FName NAME_VariableSet("VariableSet");
const FName NAME_VariableEdit("VariableEdit");
const FName NAME_SelectEval("Condition");
const FName NAME_RandomEval("Random");
const FName NAME_Event("Event");
const FName NAME_Start("Start");
const FName NAME_Finish("Finish");
FName FSUDSEditorToolkit::DialogueOutputTabName("SUDSDialogueTab");
FName FSUDSEditorToolkit::VariablesTabName("SUDSVariablesTab");
FName FSUDSEditorToolkit::DetailsTabName("SUDSDetailsTab");
FName FSUDSEditorToolkit::LogTabName("SUDSLogTab");
class FSUDSDetailRootObjectCustomization : public IDetailRootObjectCustomization
{
public:
virtual TSharedPtr<SWidget> CustomizeObjectHeader(const FDetailsObjectSet& InRootObjectSet,
const TSharedPtr<ITableRow>& InTableRow) override
{
auto Ret = SNew(STextBlock)
.Margin(FMargin(10,5))
.TextStyle(&FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SmallText"));
if (auto TN = Cast<USUDSScriptNodeText>(InRootObjectSet.RootObjects[0]))
{
Ret->SetText(FText::FromString(FString::Printf(TEXT("Speaker Line - ID: %s Source Line: %d"), *TN->GetTextID(), TN->GetSourceLineNo())));
}
return Ret;
}
virtual bool ShouldDisplayHeader(const FDetailsObjectSet& InRootObjectSet) const override
{
return InRootObjectSet.RootObjects[0]->IsA(USUDSScriptNodeText::StaticClass());
}
};
void FSUDSEditorToolkit::InitEditor(const TArray<UObject*>& InObjects)
{
if (InObjects.Num() > 0)
{
Script = Cast<USUDSScript>(InObjects[0]);
ReimportDelegateHandle = FReimportManager::Instance()->OnPostReimport().AddRaw(this, &FSUDSEditorToolkit::OnPostReimport);
const TSharedRef<FTabManager::FLayout> Layout = FTabManager::NewLayout("SUDSEditorLayout_v3")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Horizontal)
->Split
(
FTabManager::NewSplitter()
->SetSizeCoefficient(0.75f)
->SetOrientation(Orient_Horizontal)
->Split
(
FTabManager::NewSplitter()
->SetSizeCoefficient(0.6f)
->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.8f)
->AddTab("SUDSDialogueTab", ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.4f)
->AddTab("SUDSLogTab", ETabState::OpenedTab)
)
)
->Split
(
FTabManager::NewSplitter()
->SetSizeCoefficient(0.25f)
->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.6f)
->AddTab("SUDSVariablesTab", ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.4f)
->AddTab("SUDSDetailsTab", ETabState::OpenedTab)
)
)
)
);
FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, {}, "SUDSEditor", Layout, true, true, InObjects);
}
}
void FSUDSEditorToolkit::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(INVTEXT("SUDS Editor"));
InTabManager->RegisterTabSpawner(DialogueOutputTabName, FOnSpawnTab::CreateLambda([this](const FSpawnTabArgs&)
{
OutputListView = SNew(SListView<TSharedPtr<FSUDSEditorOutputRow>>)
#if ENGINE_MINOR_VERSION < 5
.ItemHeight(24)
#endif
.SelectionMode(ESelectionMode::None)
.ListItemsSource(&OutputRows)
.OnGenerateRow(this, &FSUDSEditorToolkit::OnGenerateRowForOutput)
.HeaderRow(
SNew(SHeaderRow)
+ SHeaderRow::Column("PrefixHeader")
.FillSized(PrefixColumnWidth)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew( STextBlock )
.Text( INVTEXT("") )
]
]
+ SHeaderRow::Column("LineHeader")
.FillWidth(1.0f)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew( STextBlock )
.Text( INVTEXT("") )
]
]
);
ChoicesBox = SNew(SVerticalBox);
return SNew(SDockTab)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.FillHeight(1)
[
OutputListView.ToSharedRef()
]
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Left)
.Padding(30, 15, 30, 15)
[
ChoicesBox.ToSharedRef()
]
];
}))
.SetDisplayName(INVTEXT("Dialogue Output"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef());
InTabManager->RegisterTabSpawner(VariablesTabName, FOnSpawnTab::CreateLambda([this](const FSpawnTabArgs&)
{
VariablesListView = SNew(SListView<TSharedPtr<FSUDSEditorVariableRow>>)
#if ENGINE_MINOR_VERSION < 5
.ItemHeight(28)
#endif
.SelectionMode(ESelectionMode::None)
.ListItemsSource(&VariableRows)
.OnGenerateRow(this, &FSUDSEditorToolkit::OnGenerateRowForVariable)
.HeaderRow(
SNew(SHeaderRow)
+ SHeaderRow::Column("NameHeader")
.FillSized(VarColumnWidth)
.VAlignCell(VAlign_Center)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(INVTEXT("Name"))
]
]
+ SHeaderRow::Column("ValueHeader")
.FillWidth(1.0f)
.VAlignCell(VAlign_Fill)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(INVTEXT("Value"))
]
]
);
// To ensure globals are pre-populated
UpdateVariables();
// Possibly use a SPropertyTable with a custom IPropertyTable to implement variable binding
return SNew(SDockTab)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.VAlign(VAlign_Top)
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.Padding(2)
[
SNew(SButton)
.Text(INVTEXT("Add Variable"))
.OnClicked(this, &FSUDSEditorToolkit::AddVariableClicked)
]
]
+ SVerticalBox::Slot()
.FillHeight(1.0)
[
VariablesListView.ToSharedRef()
]
];
}))
.SetDisplayName(INVTEXT("Variables"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef());
InTabManager->RegisterTabSpawner(DetailsTabName, FOnSpawnTab::CreateLambda([this](const FSpawnTabArgs&)
{
FPropertyEditorModule& PropertyEditorModule = FModuleManager::Get().LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bShowOptions = false;
DetailsViewArgs.bUpdatesFromSelection = false;
DetailsViewArgs.bShowPropertyMatrixButton = false;
DetailsViewArgs.bAllowSearch = false;
DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
DetailsViewArgs.bShowObjectLabel = false;
DetailsViewArgs.bHideSelectionTip = true;
auto DetailView = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
DetailView->SetRootObjectCustomizationInstance(MakeShareable(new FSUDSDetailRootObjectCustomization()));
TArray<UObject*> ObjectsInDetailView;
ObjectsInDetailView.Add(Script);
for (auto Node : Script->GetNodes())
{
if (auto TNode = Cast<USUDSScriptNodeText>(Node))
{
ObjectsInDetailView.Add(TNode);
}
}
DetailView->SetObjects(ObjectsInDetailView, true, true);
// Possibly use a SPropertyTable with a custom IPropertyTable to implement variable binding
return SNew(SDockTab)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.VAlign(VAlign_Top)
[
DetailView
]
];
}))
.SetDisplayName(INVTEXT("Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef());
InTabManager->RegisterTabSpawner(LogTabName, FOnSpawnTab::CreateLambda([this](const FSpawnTabArgs&)
{
TraceLog = SNew(SSUDSTraceLog);
return SNew(SDockTab)
[
TraceLog.ToSharedRef()
];
}))
.SetDisplayName(INVTEXT("Trace Log"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef());
// Set up the toolbar
FSUDSToolbarCommands::Register();
const TWeakPtr<FAssetEditorToolkit> WeakToolkit = this->AsShared();
TSharedRef<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
GetToolkitCommands(),
FToolBarExtensionDelegate::CreateRaw(this, &FSUDSEditorToolkit::ExtendToolbar, TWeakPtr<SDockTab>(TabManager->GetOwnerTab())));
AddToolbarExtender(ToolbarExtender);
GetToolkitCommands()->MapAction(FSUDSToolbarCommands::Get().StartDialogue,
FExecuteAction::CreateSP(this, &FSUDSEditorToolkit::StartDialogue),
FCanExecuteAction());
GetToolkitCommands()->MapAction(FSUDSToolbarCommands::Get().WriteBackTextIDs,
FExecuteAction::CreateSP(this, &FSUDSEditorToolkit::WriteBackTextIDs),
FCanExecuteAction());
GetToolkitCommands()->MapAction(FSUDSToolbarCommands::Get().GenerateVOAssets,
FExecuteAction::CreateSP(this, &FSUDSEditorToolkit::GenerateVOAssets),
FCanExecuteAction());
//RegenerateMenusAndToolbars();
}
void FSUDSEditorToolkit::EnsureTabsVisible()
{
// Hard requirement for the dialogue output tab to be open, so restore it if the user had
// closed it (probably by accident)
if (!TabManager->FindExistingLiveTab(DialogueOutputTabName))
{
TabManager->TryInvokeTab(DialogueOutputTabName);
}
}
void FSUDSEditorToolkit::ExtendToolbar(FToolBarBuilder& ToolbarBuilder, TWeakPtr<SDockTab> Tab)
{
if (!Tab.IsValid())
{
return;
}
ToolbarBuilder.BeginSection("CachedState");
{
ToolbarBuilder.AddToolBarButton(FSUDSToolbarCommands::Get().StartDialogue,
NAME_None, TAttribute<FText>(), TAttribute<FText>(),
FSlateIcon(
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
FAppStyle::GetAppStyleSetName(),
#else
FEditorStyle::GetStyleSetName(),
#endif
TEXT("Icons.Toolbar.Play")));
TSharedRef<SWidget> LabelSelectionBox = SNew(SComboButton)
.OnGetMenuContent(this, &FSUDSEditorToolkit::GetStartLabelMenu)
.ButtonContent()
[
SNew(STextBlock)
.ToolTipText( INVTEXT("Choose where to start dialogue from") )
.Text(this, &FSUDSEditorToolkit::GetSelectedStartLabel )
];
ToolbarBuilder.AddWidget(LabelSelectionBox);
ToolbarBuilder.AddWidget(SNew(STextBlock)
.Text(FText::FromString(TEXT("Reset Variables:")))
.Margin(FMargin(10, 7, 10, 5)));
TSharedPtr<SToolTip> ResetTooltip = IDocumentation::Get()->CreateToolTip(
FText::FromString(TEXT(
"Whether to reset variable state created by the script (note: vars manually created in this editor are never reset, delete them yourself)")),
NULL,
"",
TEXT("ResetVars"));
TSharedRef<SWidget> ResetVarsCheckbox = SNew(SCheckBox)
.ToolTip(ResetTooltip)
.IsChecked(this, &FSUDSEditorToolkit::GetResetVarsCheckState)
.OnCheckStateChanged(this, &FSUDSEditorToolkit::OnResetVarsCheckStateChanged);
ToolbarBuilder.AddWidget(ResetVarsCheckbox);
}
ToolbarBuilder.EndSection();
ToolbarBuilder.AddSeparator();
ToolbarBuilder.BeginSection("CachedState");
{
ToolbarBuilder.AddToolBarButton(FSUDSToolbarCommands::Get().WriteBackTextIDs,
NAME_None, TAttribute<FText>(), TAttribute<FText>(),
FSlateIcon(
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
FAppStyle::GetAppStyleSetName(),
#else
FEditorStyle::GetStyleSetName(),
#endif
TEXT("Icons.Toolbar.Details")));
ToolbarBuilder.AddToolBarButton(FSUDSToolbarCommands::Get().GenerateVOAssets,
NAME_None, TAttribute<FText>(), TAttribute<FText>(),
FSlateIcon(
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
FAppStyle::GetAppStyleSetName(),
#else
FEditorStyle::GetStyleSetName(),
#endif
TEXT("Icons.Toolbar.Export")));
}
ToolbarBuilder.EndSection();
}
TSharedRef<SWidget> FSUDSEditorToolkit::GetStartLabelMenu()
{
FMenuBuilder MenuBuilder(true, NULL);
const FUIAction StartAction(FExecuteAction::CreateSP(this, &FSUDSEditorToolkit::OnStartLabelSelected, FName(NAME_None)));
MenuBuilder.AddMenuEntry(INVTEXT("Beginning"), INVTEXT("Start from the beginning of the dialogue"), FSlateIcon(), StartAction);
MenuBuilder.AddSeparator();
if (IsValid(Script))
{
for (auto& LabelPair : Script->GetLabelList())
{
FUIAction LabelAction(FExecuteAction::CreateSP(this, &FSUDSEditorToolkit::OnStartLabelSelected, LabelPair.Key));
MenuBuilder.AddMenuEntry(FText::FromName(LabelPair.Key), FText(), FSlateIcon(), LabelAction);
}
}
return MenuBuilder.MakeWidget();
}
void FSUDSEditorToolkit::OnStartLabelSelected(FName Label)
{
StartLabel = Label;
}
FText FSUDSEditorToolkit::GetSelectedStartLabel() const
{
return FText::Format(INVTEXT("From: {0}"),
StartLabel.IsNone() ? INVTEXT("Beginning") : FText::FromName(StartLabel));
}
ECheckBoxState FSUDSEditorToolkit::GetResetVarsCheckState() const
{
return bResetVarsOnStart ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void FSUDSEditorToolkit::OnResetVarsCheckStateChanged(ECheckBoxState NewState)
{
bResetVarsOnStart = NewState == ECheckBoxState::Checked;
}
void FSUDSEditorToolkit::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
InTabManager->UnregisterTabSpawner("SUDSDialogueTab");
InTabManager->UnregisterTabSpawner("SUDSVariablesTab");
InTabManager->UnregisterTabSpawner("SUDSLogTab");
}
FText FSUDSEditorToolkit::GetBaseToolkitName() const
{
return INVTEXT("SUDS Script Editor");
}
FName FSUDSEditorToolkit::GetToolkitFName() const
{
return "SUDSScriptEditor";
}
FLinearColor FSUDSEditorToolkit::GetWorldCentricTabColorScale() const
{
return FLinearColor();
}
FString FSUDSEditorToolkit::GetWorldCentricTabPrefix() const
{
return "SUDS Script ";
}
void FSUDSEditorToolkit::UserEditVariable(const FName& Name, FSUDSValue Value)
{
FName GlobalName;
if (USUDSLibrary::IsDialogueVariableGlobal(Name, GlobalName))
{
USUDSSubsystem::Test_DummyGlobalVariables.Add(GlobalName, Value);
OnDialogueUserEditedVar(nullptr, Name, Value);
}
else
{
// Update manual overrides if it's one of these
if (ManualOverrideVariables.Contains(Name))
{
ManualOverrideVariables[Name] = Value;
}
// This will cause a refresh
if (Dialogue)
Dialogue->SetVariable(Name, Value);
else
{
// This won't get called if dialogue is not initialised, so call it to get log & refresh
OnDialogueUserEditedVar(nullptr, Name, Value);
}
}
}
void FSUDSEditorToolkit::DeleteVariable(const FName& Name)
{
FName GlobalName;
if (USUDSLibrary::IsDialogueVariableGlobal(Name, GlobalName))
{
USUDSSubsystem::Test_DummyGlobalVariables.Remove(GlobalName);
}
else
{
ManualOverrideVariables.Remove(Name);
if (Dialogue)
Dialogue->UnSetVariable(Name);
}
UpdateVariables();
}
void FSUDSEditorToolkit::OnClose()
{
FAssetEditorToolkit::OnClose();
FReimportManager::Instance()->OnPostReimport().Remove(ReimportDelegateHandle);
ReimportDelegateHandle.Reset();
DestroyDialogue();
}
void FSUDSEditorToolkit::StartDialogue()
{
// In case the user closed important tabs
EnsureTabsVisible();
Clear();
if (!Dialogue)
{
// Custom creation of USUDSDialogue, we want it to exist only for this window.
// Mark as a root object (we'll clear it on close)
{
// Have to guard vs GC
FGCScopeGuard GCGuard;
const FName Name = MakeUniqueObjectName(GetTransientPackage(),
USUDSDialogue::StaticClass(),
Script->GetFName());
Dialogue = NewObject<USUDSDialogue>(GetTransientPackage(),
Name, RF_Transient | RF_MarkAsRootSet);
}
Dialogue->Initialise(Script);
Dialogue->InternalOnChoice.BindSP(this, &FSUDSEditorToolkit::OnDialogueChoice);
Dialogue->InternalOnEvent.BindSP(this, &FSUDSEditorToolkit::OnDialogueEvent);
Dialogue->InternalOnFinished.BindSP(this, &FSUDSEditorToolkit::OnDialogueFinished);
Dialogue->InternalOnProceeding.BindSP(this, &FSUDSEditorToolkit::OnDialogueProceeding);
Dialogue->InternalOnStarting.BindSP(this, &FSUDSEditorToolkit::OnDialogueStarting);
Dialogue->InternalOnSpeakerLine.BindSP(this, &FSUDSEditorToolkit::OnDialogueSpeakerLine);
Dialogue->InternalOnSetVar.BindSP(this, &FSUDSEditorToolkit::OnDialogueSetVar);
Dialogue->InternalOnSetVarByCode.BindSP(this, &FSUDSEditorToolkit::OnDialogueUserEditedVar);
Dialogue->InternalOnSelectEval.BindSP(this, &FSUDSEditorToolkit::OnDialogueSelectEval);
}
else
{
// Reset things manually so we can set manual override vars
if (bResetVarsOnStart)
Dialogue->ResetState();
}
// Set manual override vars before we start
for (auto& Pair : ManualOverrideVariables)
{
Dialogue->SetVariable(Pair.Key, Pair.Value);
}
Dialogue->Restart(false, StartLabel);
UpdateVariables();
}
void FSUDSEditorToolkit::Clear()
{
OutputRows.Empty();
if (OutputListView.IsValid())
{
OutputListView->RequestListRefresh();
}
VariableRows.Empty();
if (VariablesListView.IsValid())
{
VariablesListView->RequestListRefresh();
}
//TraceLog->ClearMessages();
}
void FSUDSEditorToolkit::DestroyDialogue()
{
if (Dialogue)
{
// Handle garbage collection of our UObject
{
FGCScopeGuard GCGuard;
Dialogue->RemoveFromRoot();
Dialogue->MarkAsGarbage();
}
Dialogue = nullptr;
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
}
void FSUDSEditorToolkit::UpdateOutput()
{
if (OutputListView.IsValid())
{
OutputListView->RequestListRefresh();
OutputListView->ScrollToBottom();
}
}
void FSUDSEditorToolkit::UpdateChoiceButtons()
{
USUDSDialogue* D = Dialogue;
ChoicesBox->ClearChildren();
if (!D->IsEnded())
{
if (D->IsSimpleContinue())
{
ChoicesBox->AddSlot()
.Padding(0, 0 ,0 , 5)
[
SNew(SButton)
.HAlign(HAlign_Left)
.Text(INVTEXT("(Continue...)"))
.OnClicked_Lambda([D]()
{
D->Continue();
return FReply::Handled();
})
];
}
else
{
for (int i = 0; i < D->GetNumberOfChoices(); ++i)
{
ChoicesBox->AddSlot()
.Padding(0, 0 ,0 , 5)
[
SNew(SButton)
.HAlign(HAlign_Left)
.Text(D->GetChoiceText(i))
.OnClicked_Lambda([D, i]()
{
D->Choose(i);
return FReply::Handled();
})
];
}
}
}
}
void FSUDSEditorToolkit::OnDialogueChoice(USUDSDialogue* D, int ChoiceIndex, int LineNo)
{
if (!D->IsSimpleContinue())
{
AddDialogueStep(NAME_Choice, LineNo,
FText::Format(INVTEXT("[{1}] {0}"), D->GetChoiceText(ChoiceIndex), ChoiceIndex),
INVTEXT("Choice"));
}
}
void FSUDSEditorToolkit::OnDialogueEvent(USUDSDialogue* D, FName EventName, const TArray<FSUDSValue>& Args, int LineNo)
{
#if ENGINE_MAJOR_VERSION ==5 && ENGINE_MINOR_VERSION >= 8
TStringBuilder<256> B;
#else
FStringBuilderBase B;
#endif
if (Args.Num() > 0)
{
for (auto& Arg : Args)
{
if (B.Len() > 0)
{
B.Appendf(TEXT(", %s"), *Arg.ToString());
}
else
{
B.Append("( ");
B.Append(Arg.ToString());
}
}
B.Append(" )");
}
FText ArgText = FText::FromString(B.ToString());
AddDialogueStep(NAME_Event, LineNo,
FText::FormatOrdered(INVTEXT("{0} {1}"), FText::FromName(EventName), ArgText),
INVTEXT("Event"));
}
void FSUDSEditorToolkit::OnDialogueFinished(USUDSDialogue* D)
{
AddDialogueStep(NAME_Finish, 0,
INVTEXT("Dialogue Finished"),
INVTEXT("Finish"));
UpdateChoiceButtons();
}
void FSUDSEditorToolkit::OnDialogueProceeding(USUDSDialogue* D)
{
}
void FSUDSEditorToolkit::OnDialogueStarting(USUDSDialogue* D, FName LabelName)
{
FString LabelStr = LabelName.IsNone() ? FString("beginning") : LabelName.ToString();
AddDialogueStep(NAME_Start, 0,
FText::Format(INVTEXT("Starting from {0}"), FText::FromString(LabelStr)),
INVTEXT("Start"));
}
void FSUDSEditorToolkit::OnDialogueSpeakerLine(USUDSDialogue* D, int LineNo)
{
AddDialogueStep(NAME_SpeakerLine, LineNo , D->GetText(),D->GetSpeakerDisplayName());
UpdateChoiceButtons();
}
void FSUDSEditorToolkit::AddOutputRow(const FText& Prefix,
const FText& Line,
const FSlateColor& PrefixColour,
const FSlateColor& LineColour)
{
// Stupid table changes any colours I give it, white = dark grey
// Can't figure out where it's coming from, I f**king hate Slate
const FSlateColor BgColour = (OutputRows.Num() % 2) > 0 ? FSlateColor(FLinearColor::White) : FSlateColor(FLinearColor(0.9f, 0.9f, 0.9f, 1));
OutputRows.Add(MakeShareable(
new FSUDSEditorOutputRow(Prefix, Line, PrefixColour, LineColour, BgColour)));
UpdateOutput();
}
void FSUDSEditorToolkit::AddTraceLogRow(const FName& Category, int SourceLineNo, const FString& Message)
{
FSlateColor Colour = GetColourForCategory(Category);
if (TraceLog)
{
TraceLog->AppendMessage(Category, SourceLineNo, Message, Colour);
}
}
FSlateColor FSUDSEditorToolkit::GetColourForCategory(const FName& Category)
{
if (Category == NAME_SpeakerLine)
{
return SpeakerColour;
}
else if (Category == NAME_Choice)
{
return ChoiceColour;
}
else if (Category == NAME_Event)
{
return EventColour;
}
else if (Category == NAME_VariableSet)
{
return VarSetColour;
}
else if (Category == NAME_VariableEdit)
{
return VarEditColour;
}
else if (Category == NAME_Start)
{
return StartColour;
}
else if (Category == NAME_Finish)
{
return FinishColour;
}
else if (Category == NAME_SelectEval)
{
return SelectColour;
}
return FSlateColor::UseForeground();
}
void FSUDSEditorToolkit::AddDialogueStep(const FName& Category, int SourceLineNo, const FText& Description, const FText& Prefix)
{
if (Category == NAME_SpeakerLine)
{
AddOutputRow(Prefix, Description, SpeakerColour, FSlateColor::UseForeground());
AddTraceLogRow(Category, SourceLineNo, FString::Printf(TEXT("%s: %s"), *Prefix.ToString(), *Description.ToString()));
}
else if (Category == NAME_Choice)
{
AddOutputRow(Prefix, Description, ChoiceColour, ChoiceColour);
AddTraceLogRow(Category, SourceLineNo, Description.ToString());
}
else if (Category == NAME_Start)
{
AddOutputRow(Prefix, Description, StartColour, StartColour);
AddTraceLogRow(Category, SourceLineNo, Description.ToString());
}
else if (Category == NAME_Finish)
{
AddOutputRow(Prefix, Description, FinishColour, FinishColour);
AddTraceLogRow(Category, SourceLineNo, Description.ToString());
}
else
{
AddTraceLogRow(Category, SourceLineNo, Description.ToString());
}
}
FReply FSUDSEditorToolkit::AddVariableClicked()
{
TSharedRef<SWindow> Window = SNew(SWindow)
.Title(INVTEXT("Add Variable"))
.IsPopupWindow(true)
.SupportsMaximize(false)
.SupportsMinimize(false)
.CreateTitleBar(true)
.SizingRule(ESizingRule::Autosized)
.FocusWhenFirstShown(true)
.MinWidth(400)
.ActivationPolicy(EWindowActivationPolicy::FirstShown);
bool bConfirmed = false;
ESUDSValueType ValType = ESUDSValueType::Int;
FText VarNameText = FText::FromString("NewVar");
Window->SetContent(
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(0.4)
[
SNew(STextBlock)
.Text(INVTEXT("Name"))
]
+SHorizontalBox::Slot()
.FillWidth(0.6)
[
SNew(SEditableTextBox)
.MinDesiredWidth(150)
.SelectAllTextWhenFocused(true)
.Text(VarNameText)
.OnTextCommitted_Lambda([&VarNameText](const FText& NewText, ETextCommit::Type CommitType)
{
VarNameText = NewText;
})
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(0.4)
[
SNew(STextBlock)
.Text(INVTEXT("Type"))
]
+SHorizontalBox::Slot()
.FillWidth(0.6)
[
SNew(SComboButton)
.OnGetMenuContent_Lambda([&ValType]
{
FMenuBuilder MenuBuilder(true, NULL);
// NumEnums() - 1 because the last value is the autogen _MAX value
for (int i = 0; i < StaticEnum<ESUDSValueType>()->NumEnums() - 1; ++i)
{
ESUDSValueType Val = (ESUDSValueType)StaticEnum<ESUDSValueType>()->GetValueByIndex(i);
if (Val != ESUDSValueType::Empty && Val != ESUDSValueType::Variable)
{
const FUIAction Action(FExecuteAction::CreateLambda([Val, &ValType]()
{
ValType = Val;
}));
MenuBuilder.AddMenuEntry(FText::FromString(StaticEnum<ESUDSValueType>()->GetNameStringByIndex(i)),
FText(),
FSlateIcon(),
Action);
}
}
return MenuBuilder.MakeWidget();
})
.ButtonContent()
[
SNew(STextBlock)
.Text_Lambda([&ValType]
{
return FText::FromString(StaticEnum<ESUDSValueType>()->GetNameStringByValue((int64)ValType));
})
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 5, 0, 3)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.Padding(2)
[
SNew(SButton)
.Text(INVTEXT("OK"))
.OnClicked_Lambda([&bConfirmed, Window, VarNameText]()
{
if (!VarNameText.IsEmptyOrWhitespace())
{
bConfirmed = true;
Window->RequestDestroyWindow();
}
return FReply::Handled();
})
]
+SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.Text(INVTEXT("Cancel"))
.OnClicked_Lambda([&bConfirmed, Window]()
{
bConfirmed = false;
Window->RequestDestroyWindow();
return FReply::Handled();
})
]
]
);
FVector2D Pos = FSlateApplication::Get().GetCursorPos();
Pos.X -= 230;
Pos.Y += 100;
Window->MoveWindowTo(Pos);
// This doesn't return until closed
GEditor->EditorAddModalWindow(Window);
if (bConfirmed)
{
FName VarName(VarNameText.ToString().TrimStartAndEnd());
const FSUDSValue Val(ValType);
FName GlobalVarName;
if (USUDSLibrary::IsDialogueVariableGlobal(VarName, GlobalVarName))
{
USUDSSubsystem::Test_DummyGlobalVariables.Add(GlobalVarName, Val);
OnDialogueUserEditedVar(nullptr, VarName, Val);
}
else
{
ManualOverrideVariables.Add(VarName, Val);
if (Dialogue)
{
// This will cause a refresh
Dialogue->SetVariable(VarName, Val);
}
else
{
// This won't get called if dialogue is not initialised, so call it to get log & refresh
OnDialogueUserEditedVar(nullptr, VarName, Val);
}
}
}
return FReply::Handled();
}
void FSUDSEditorToolkit::UpdateVariables()
{
VariableRows.Empty();
// From dialogue (will contain manual overrides already)
if (Dialogue)
{
for (auto& Pair : Dialogue->GetVariables())
{
VariableRows.Add(MakeShareable(
new FSUDSEditorVariableRow(Pair.Key, Pair.Value, ManualOverrideVariables.Contains(Pair.Key))));
}
}
else
{
// Manual overrides can occur before dialogue is started
// Otherwise, they will be in the dialogue
for (auto& Pair : ManualOverrideVariables)
{
VariableRows.Add(MakeShareable(new FSUDSEditorVariableRow(Pair.Key, Pair.Value, true)));
}
}
// Also do global vars
for (auto& Pair : USUDSSubsystem::Test_DummyGlobalVariables)
{
VariableRows.Add(MakeShareable(
new FSUDSEditorVariableRow(
// We need to insert the "global." prefix back in since it's removed in the global var list
FName(FString::Printf(TEXT("global.%s"), *Pair.Key.ToString())),
Pair.Value,
true)));
}
VariableRows.Sort();
if (VariablesListView.IsValid())
{
VariablesListView->RequestListRefresh();
}
}
void FSUDSEditorToolkit::OnPostReimport(UObject* Object, bool bSuccess)
{
if (Object == Script)
{
// Destroy any dialogue instance, will not be valid post-import
DestroyDialogue();
Clear();
}
}
void FSUDSEditorToolkit::OnDialogueSetVar(USUDSDialogue* D,
FName VariableName,
const FSUDSValue& ToValue,
const FString& ExpressionStr,
int LineNo)
{
FText Description;
if (ExpressionStr.Len() > 0)
{
Description = FText::Format(INVTEXT("{0} = {1} = {2}"),
FText::FromName(VariableName),
FText::FromString(ExpressionStr),
FText::FromString(ToValue.ToString()));
}
else
{
Description = FText::Format(INVTEXT("{0} = {1}"),
FText::FromName(VariableName),
FText::FromString(ToValue.ToString()));
}
AddDialogueStep(NAME_VariableSet,
LineNo,
Description,
INVTEXT("Set Variable"));
UpdateVariables();
}
void FSUDSEditorToolkit::OnDialogueUserEditedVar(USUDSDialogue* D, FName VariableName, const FSUDSValue& ToValue)
{
// User-edited value
const FText Description = FText::Format(INVTEXT("{0} = {1}"),
FText::FromName(VariableName),
FText::FromString(ToValue.ToString()));
AddDialogueStep(NAME_VariableEdit,
0,
Description,
INVTEXT("Edit Variable"));
UpdateVariables();
}
void FSUDSEditorToolkit::OnDialogueSelectEval(USUDSDialogue* D,
const FString& ExpressionStr,
bool bSuccess,
int LineNo)
{
AddDialogueStep(NAME_SelectEval,
LineNo,
FText::Format(INVTEXT("{0} = {1}"),
FText::FromString(ExpressionStr),
bSuccess ? INVTEXT("true") : INVTEXT("false")),
INVTEXT("Conditional"));
}
void FSUDSEditorToolkit::OnDialogueRandomEval(USUDSDialogue* D,
const int RandomOutcome,
int LineNo)
{
AddDialogueStep(NAME_RandomEval,
LineNo,
FText::Format(INVTEXT(": {0}"),
RandomOutcome),
INVTEXT("Random"));
}
TSharedRef<ITableRow> FSUDSEditorToolkit::OnGenerateRowForOutput(
TSharedPtr<FSUDSEditorOutputRow> Row,
const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew( SSUDSEditorOutputItem, OwnerTable )
.Prefix(Row->Prefix)
.PrefixColour(Row->PrefixColour)
.Line(Row->Line)
.LineColour(Row->LineColour)
.BgColour(Row->BgColour)
.InitialWidth( PrefixColumnWidth );
}
TSharedRef<ITableRow> FSUDSEditorToolkit::OnGenerateRowForVariable(TSharedPtr<FSUDSEditorVariableRow> Row,
const TSharedRef<STableViewBase>& Table)
{
return SNew( SSUDSEditorVariableItem, Table )
.VariableName(Row->Name)
.VariableValue(Row->Value)
.bIsManualOverride(Row->bIsManualOverride)
.Parent(this)
.InitialWidth( VarColumnWidth );
}
void FSUDSEditorToolkit::WriteBackTextIDs()
{
if (FMessageDialog::Open(EAppMsgType::YesNo,
FText::FromString(
"Are you sure you want to write string keys back to this script?"))
== EAppReturnType::Yes)
{
FSUDSMessageLogger::ClearMessages();
FSUDSMessageLogger Logger;
FSUDSEditorScriptTools::WriteBackTextIDs(Script, Logger);
}
}
void FSUDSEditorToolkit::GenerateVOAssets()
{
if (FMessageDialog::Open(EAppMsgType::YesNo,
FText::FromString(
"Are you sure you want to generate Dialogue Voice / Dialogue Wave assets for this script?"))
== EAppReturnType::Yes)
{
EObjectFlags Flags = RF_Public | RF_Standalone | RF_Transactional;
FSUDSMessageLogger::ClearMessages();
FSUDSMessageLogger Logger;
FSUDSEditorVoiceOverTools::GenerateAssets(Script, Flags, &Logger);
}
}
void SSUDSEditorVariableItem::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
InitialWidth = InArgs._InitialWidth;
VariableName = InArgs._VariableName;
VariableValue = InArgs._VariableValue;
bIsManualOverride = InArgs._bIsManualOverride;
Parent = InArgs._Parent;
SMultiColumnTableRow< TSharedPtr< FString > >::Construct( SMultiColumnTableRow< TSharedPtr< FString > >::FArguments(), InOwnerTableView );
}
TSharedRef<SWidget> SSUDSEditorVariableItem::GenerateWidgetForColumn(const FName& ColumnName)
{
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
const FSlateFontInfo PropertyFont = FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"));
const FSlateBrush* BorderBrush = FAppStyle::GetBrush("ToolPanel.GroupBorder");
#else
const FSlateFontInfo PropertyFont = FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"));
const FSlateBrush* BorderBrush = FEditorStyle::GetBrush("ToolPanel.GroupBorder");
#endif
const FSlateColor NormalBgColour = FSlateColor(FLinearColor::White);
const FSlateColor ManualOverrideBgColour = FSlateColor(FLinearColor(0.9f,0.7f,1,1));
if (ColumnName == "NameHeader")
{
const FMargin BoxMargin(0, 2, 0, 1.333f);
return SNew(SBorder)
.BorderImage(BorderBrush)
.BorderBackgroundColor(bIsManualOverride ? ManualOverrideBgColour : NormalBgColour)
.Padding(BoxMargin)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0)
.Padding(10, 5, 5, 5)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Font(PropertyFont)
.Text(FText::FromName(VariableName))
]
];
}
else
{
TSharedPtr<SWidget> ValueWidget;
FMargin InnerPadding = FMargin(5, 2, 5, 2);
switch (VariableValue.GetType())
{
case ESUDSValueType::Int:
ValueWidget = SNew(SNumericEntryBox<int32>)
.Value(VariableValue.GetIntValue())
.OnValueCommitted_Lambda([this] (const int32 InValue, ETextCommit::Type)
{
Parent->UserEditVariable(VariableName, InValue);
})
.MinDesiredValueWidth(60)
.Font(PropertyFont);
break;
case ESUDSValueType::Float:
ValueWidget = SNew(SNumericEntryBox<float>)
.Value(VariableValue.GetFloatValue())
.OnValueCommitted_Lambda([this] (const float InValue, ETextCommit::Type)
{
Parent->UserEditVariable(VariableName, InValue);
})
.MinDesiredValueWidth(60)
.Font(PropertyFont);
break;
case ESUDSValueType::Boolean:
ValueWidget = SNew(SCheckBox)
.IsChecked(VariableValue.GetBooleanValue() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
.OnCheckStateChanged_Lambda([this] (ECheckBoxState NewState)
{
Parent->UserEditVariable(VariableName, NewState == ECheckBoxState::Checked);
});
InnerPadding.Top = InnerPadding.Bottom = 2;
break;
case ESUDSValueType::Gender:
ValueWidget = SNew(SComboButton)
.OnGetMenuContent(this, &SSUDSEditorVariableItem::GetGenderMenu)
.ButtonContent()
[
SNew(STextBlock)
.Text(FText::FromString(VariableValue.ToString()))
.Font(PropertyFont)
];
InnerPadding.Top = InnerPadding.Bottom = 1;
break;
case ESUDSValueType::Text:
ValueWidget = SNew(SEditableTextBox)
.IsReadOnly(false)
.Text(VariableValue.GetTextValue())
.Font(PropertyFont)
.OnTextCommitted_Lambda([this] (const FText& InValue, ETextCommit::Type)
{
Parent->UserEditVariable(VariableName, InValue);
});
break;
case ESUDSValueType::Name:
ValueWidget = SNew(SEditableTextBox)
.IsReadOnly(false)
.Text(FText::FromString(VariableValue.GetNameValue().ToString()))
.Font(PropertyFont)
.OnTextCommitted_Lambda([this] (const FText& InValue, ETextCommit::Type)
{
Parent->UserEditVariable(VariableName, FSUDSValue(FName(InValue.ToString()), false));
});
break;
case ESUDSValueType::Variable:
case ESUDSValueType::Empty:
default:
ValueWidget = SNew(STextBlock);
break;
};
const FMargin BoxMargin(2, 1, 0, 1);
return SNew(SBox)
.HeightOverride(26)
.Padding(BoxMargin)
[
SNew(SBorder)
.BorderImage(BorderBrush)
.BorderBackgroundColor(bIsManualOverride ? ManualOverrideBgColour : NormalBgColour)
.VAlign(VAlign_Center)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(InnerPadding)
.AutoWidth()
[
ValueWidget.ToSharedRef()
]
+ SHorizontalBox::Slot()
.Padding(5,2,5,2)
.FillWidth(1.0)
.HAlign(HAlign_Right)
[
SNew(SButton)
.ButtonStyle( FAppStyle::Get(), "SimpleButton" )
.OnClicked_Lambda([this]
{
// Delete click
Parent->DeleteVariable(VariableName);
return FReply::Handled();
} )
.ContentPadding(0)
.IsFocusable(false)
[
SNew( SImage )
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
.Image( FAppStyle::GetBrush("Icons.Delete") )
#else
.Image( FEditorStyle::GetBrush("Icons.Delete") )
#endif
.ColorAndOpacity( FSlateColor::UseForeground() )
]
]
]
];
}
}
TSharedRef<SWidget> SSUDSEditorVariableItem::GetGenderMenu()
{
FMenuBuilder MenuBuilder(true, NULL);
#if ENGINE_MAJOR_VERSION ==5 && ENGINE_MINOR_VERSION >= 8
// StaticEnum<ETextGender>() doesn't exist anymore so we have to do this manually
const FUIAction Action0(FExecuteAction::CreateSP(this, &SSUDSEditorVariableItem::OnGenderSelected, ETextGender::Feminine));
MenuBuilder.AddMenuEntry(FText::FromString(LexToString(ETextGender::Feminine)),
FText(),
FSlateIcon(),
Action0);
const FUIAction Action1(FExecuteAction::CreateSP(this, &SSUDSEditorVariableItem::OnGenderSelected, ETextGender::Masculine));
MenuBuilder.AddMenuEntry(FText::FromString(LexToString(ETextGender::Masculine)),
FText(),
FSlateIcon(),
Action1);
const FUIAction Action2(FExecuteAction::CreateSP(this, &SSUDSEditorVariableItem::OnGenderSelected, ETextGender::Neuter));
MenuBuilder.AddMenuEntry(FText::FromString(LexToString(ETextGender::Neuter)),
FText(),
FSlateIcon(),
Action2);
#else
// NumEnums() - 1 because the last value is the autogen _MAX value
for (int i = 0; i < StaticEnum<ETextGender>()->NumEnums() - 1; ++i)
{
ETextGender Val = (ETextGender)StaticEnum<ETextGender>()->GetValueByIndex(i);
const FUIAction Action(FExecuteAction::CreateSP(this, &SSUDSEditorVariableItem::OnGenderSelected, Val));
MenuBuilder.AddMenuEntry(FText::FromString(StaticEnum<ETextGender>()->GetNameStringByIndex(i)),
FText(),
FSlateIcon(),
Action);
}
#endif
return MenuBuilder.MakeWidget();
}
void SSUDSEditorVariableItem::OnGenderSelected(ETextGender NewGender)
{
Parent->UserEditVariable(VariableName, NewGender);
}
FVector2D SSUDSEditorVariableItem::ComputeDesiredSize(float X) const
{
// ItemHeight(28) at table level is totally not working for some reason, so fudge it here
FVector2D Desired = SMultiColumnTableRow<TSharedPtr<FString, ESPMode::ThreadSafe>>::ComputeDesiredSize(X);
Desired.Y = 28;
return Desired;
}
void SSUDSEditorOutputItem::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
InitialWidth = InArgs._InitialWidth;
Prefix = InArgs._Prefix;
PrefixColour = InArgs._PrefixColour;
Line = InArgs._Line;
LineColour = InArgs._LineColour;
SetBorderBackgroundColor(InArgs._BgColour);
SMultiColumnTableRow< TSharedPtr< FString > >::Construct( SMultiColumnTableRow< TSharedPtr< FString > >::FArguments(), InOwnerTableView );
}
TSharedRef<SWidget> SSUDSEditorOutputItem::GenerateWidgetForColumn(const FName& ColumnName)
{
if (ColumnName == "PrefixHeader")
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0)
.Padding(10, 5, 10, 5)
[
SNew(STextBlock)
.Text(Prefix)
.ColorAndOpacity(PrefixColour)
];
}
else
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0)
.Padding(10, 5, 10, 5)
[
SNew(STextBlock)
.Text(Line)
.AutoWrapText(true)
.ColorAndOpacity(LineColour)
];
}
}
FSUDSTraceLogMarshaller::FSUDSTraceLogMarshaller()
{
}
void FSUDSTraceLogMarshaller::SetText(const FString& SourceString, FTextLayout& TargetTextLayout)
{
static const FName LogNormalStyle(TEXT("Log.Normal"));
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
const FTextBlockStyle& OrigStyle = FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>(LogNormalStyle);
#else
const FTextBlockStyle& OrigStyle = FEditorStyle::Get().GetWidgetStyle<FTextBlockStyle>(LogNormalStyle);
#endif
for (const auto& Msg : Messages)
{
// Get base style & copy
FTextBlockStyle Style = OrigStyle;
Style.ColorAndOpacity = Msg->Colour;
TArray<TSharedRef<IRun>> Runs;
Runs.Add(FSlateTextRun::Create(FRunInfo(), Msg->Message, Style));
TargetTextLayout.AddLine(FTextLayout::FNewLineData(Msg->Message, Runs));
}
}
void FSUDSTraceLogMarshaller::GetText(FString& TargetString, const FTextLayout& SourceTextLayout)
{
SourceTextLayout.GetAsText(TargetString);
}
void FSUDSTraceLogMarshaller::AppendMessage(FName InCategory, int LineNo, const FString& Message, const FSlateColor& Colour)
{
const int MinCategorySize = 12;
const FString CatStr = InCategory.ToString();
int CatLen = CatStr.Len();
const FString CatPadding = CatLen < MinCategorySize ? FString::ChrN(MinCategorySize - CatLen, ' ') : "";
const FString LineNoStr = LineNo > 0 ? FString::Printf(TEXT("L%04d"), LineNo) : " ";
const FString ConcatLine = FString::Printf(TEXT("%s[%s] %s %s"), *CatPadding, *CatStr, *LineNoStr, *Message);
Messages.Add(MakeShareable(new FSUDSTraceLogMessage(InCategory, ConcatLine, Colour)));
MakeDirty();
}
void FSUDSTraceLogMarshaller::ClearMessages()
{
Messages.Empty();
MakeDirty();
}
void SSUDSTraceLog::Construct(const FArguments& InArgs)
{
TraceLogMarshaller = MakeShareable(new FSUDSTraceLogMarshaller());
TraceLogTextBox = SNew(SMultiLineEditableTextBox)
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
.Style(FAppStyle::Get(), "Log.TextBox")
#else
.Style(FEditorStyle::Get(), "Log.TextBox")
.TextStyle(FEditorStyle::Get(), "Log.Normal")
#endif
.Marshaller(TraceLogMarshaller)
.IsReadOnly(true)
.AutoWrapText(true)
.OnVScrollBarUserScrolled(this, &SSUDSTraceLog::OnUserScrolled)
.AlwaysShowScrollbars(true);
ChildSlot
.Padding(3)
[
SNew(SVerticalBox)
// could add a filter here like output log
// log area
+SVerticalBox::Slot()
.FillHeight(1)
[
TraceLogTextBox.ToSharedRef()
]
];
}
void SSUDSTraceLog::OnUserScrolled(float X)
{
// revert to auto scroll when near the bottom
bIsUserScrolled = X < (1.0 - SMALL_NUMBER);
}
void SSUDSTraceLog::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
// We seem to have to do this in Tick(), trying to do it after appending never works
if (!bIsUserScrolled)
{
ScrollToEnd();
}
}
void SSUDSTraceLog::AppendMessage(FName InCategory, int LineNo, const FString& Message, const FSlateColor& Colour)
{
TraceLogMarshaller->AppendMessage(InCategory, LineNo, Message, Colour);
}
void SSUDSTraceLog::ClearMessages()
{
TraceLogMarshaller->ClearMessages();
}
void SSUDSTraceLog::ScrollToEnd()
{
TraceLogTextBox->ScrollTo(ETextLocation::EndOfDocument);
bIsUserScrolled = false;
}