diff --git a/Resources/Icons/Discord.png b/Resources/Icons/Discord.png new file mode 100644 index 000000000..68a6a9fba Binary files /dev/null and b/Resources/Icons/Discord.png differ diff --git a/Resources/Icons/Docs.png b/Resources/Icons/Docs.png new file mode 100644 index 000000000..8a1766680 Binary files /dev/null and b/Resources/Icons/Docs.png differ diff --git a/Resources/Icons/Github.png b/Resources/Icons/Github.png new file mode 100644 index 000000000..4f4322cf4 Binary files /dev/null and b/Resources/Icons/Github.png differ diff --git a/Source/FlowEditor/FlowEditor.Build.cs b/Source/FlowEditor/FlowEditor.Build.cs index bf270ec93..8237a9360 100644 --- a/Source/FlowEditor/FlowEditor.Build.cs +++ b/Source/FlowEditor/FlowEditor.Build.cs @@ -52,6 +52,7 @@ public FlowEditor(ReadOnlyTargetRules target) : base(target) "RenderCore", "Sequencer", "SequencerCore", + "Settings", "Slate", "SlateCore", "SourceControl", @@ -59,4 +60,4 @@ public FlowEditor(ReadOnlyTargetRules target) : base(target) "UnrealEd" ]); } -} \ No newline at end of file +} diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index 09906bf8c..bcee4f8b7 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -6,8 +6,10 @@ #include "Asset/FlowAssetEditor.h" #include "Asset/FlowAssetIndexer.h" #include "Graph/FlowGraphConnectionDrawingPolicy.h" +#include "Graph/FlowGraphEditorSettings.h" #include "Graph/FlowGraphPinFactory.h" #include "Graph/FlowGraphSettings.h" +#include "Utils/SFlowWelcomeWindow.h" #include "Utils/SLevelEditorFlow.h" #include "MovieScene/FlowTrackEditor.h" #include "Nodes/AssetTypeActions_FlowNodeBlueprint.h" @@ -47,11 +49,14 @@ #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraphUtilities.h" #include "IAssetSearchModule.h" +#include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "ISequencerChannelInterface.h" // ignore Rider's false "unused include" warning #include "ISequencerModule.h" #include "LevelEditor.h" +#include "Misc/CoreDelegates.h" #include "Modules/ModuleManager.h" +#include "Widgets/SWindow.h" static FName AssetSearchModuleName = TEXT("AssetSearch"); @@ -91,6 +96,18 @@ void FFlowEditorModule::StartupModule() RegisterDetailCustomizations(); + if (GIsEditor && !IsRunningCommandlet()) + { + if (FSlateApplication::IsInitialized()) + { + TryOpenWelcomeWindow(); + } + else + { + PostEngineInitHandle = FCoreDelegates::OnPostEngineInit.AddRaw(this, &FFlowEditorModule::HandlePostEngineInit); + } + } + // register asset indexers if (FModuleManager::Get().IsModuleLoaded(AssetSearchModuleName)) { @@ -115,6 +132,12 @@ void FFlowEditorModule::RegisterForAssetChanges() void FFlowEditorModule::ShutdownModule() { + if (PostEngineInitHandle.IsValid()) + { + FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitHandle); + PostEngineInitHandle.Reset(); + } + FFlowEditorStyle::Shutdown(); UnregisterDetailCustomizations(); @@ -349,6 +372,46 @@ void FFlowEditorModule::OnAssetRenamed(const FAssetData& AssetData, const FStrin OnAssetUpdated(AssetData); } +void FFlowEditorModule::HandlePostEngineInit() +{ + if (PostEngineInitHandle.IsValid()) + { + FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitHandle); + PostEngineInitHandle.Reset(); + } + + TryOpenWelcomeWindow(); +} + +void FFlowEditorModule::TryOpenWelcomeWindow() +{ + if (!FSlateApplication::IsInitialized()) + { + return; + } + + UFlowGraphEditorSettings* EditorSettings = GetMutableDefault(); + if (!EditorSettings || !EditorSettings->bShowWelcomeWindowOnStartup) + { + return; + } + + const TSharedRef WelcomeWindow = SNew(SWindow) + .Title(LOCTEXT("FlowWelcomeWindowTitle", "Welcome to Flow Graph")) + .ClientSize(FVector2D(880.f, 520.f)) + .SizingRule(ESizingRule::UserSized) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .SupportsMaximize(false) + .SupportsMinimize(false) + [ + SNew(SFlowWelcomeWindow) + ]; + FSlateApplication::Get().AddWindow(WelcomeWindow); + + EditorSettings->bShowWelcomeWindowOnStartup = false; + EditorSettings->SaveConfig(); +} + #undef LOCTEXT_NAMESPACE -IMPLEMENT_MODULE(FFlowEditorModule, FlowEditor) \ No newline at end of file +IMPLEMENT_MODULE(FFlowEditorModule, FlowEditor) diff --git a/Source/FlowEditor/Private/FlowEditorStyle.cpp b/Source/FlowEditor/Private/FlowEditorStyle.cpp index f37844996..d1d0476d3 100644 --- a/Source/FlowEditor/Private/FlowEditorStyle.cpp +++ b/Source/FlowEditor/Private/FlowEditorStyle.cpp @@ -54,6 +54,10 @@ void FFlowEditorStyle::Initialize() StyleSet->Set("Flow.Node.Body", new BOX_BRUSH("Icons/FlowNode_Body", FMargin(16.f/64.f))); StyleSet->Set("Flow.Node.ActiveShadow", new BOX_BRUSH("Icons/FlowNode_Shadow_Active", FMargin(18.0f/64.0f))); StyleSet->Set("Flow.Node.WasActiveShadow", new BOX_BRUSH("Icons/FlowNode_Shadow_WasActive", FMargin(18.0f/64.0f))); + + StyleSet->Set("FlowWelcome.Docs", new IMAGE_BRUSH(TEXT("Icons/Docs"), Icon20)); + StyleSet->Set("FlowWelcome.GitHub", new IMAGE_BRUSH(TEXT("Icons/Github"), Icon20)); + StyleSet->Set("FlowWelcome.Discord", new IMAGE_BRUSH(TEXT("Icons/Discord"), Icon20)); FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); }; diff --git a/Source/FlowEditor/Private/Graph/FlowGraphEditorSettings.cpp b/Source/FlowEditor/Private/Graph/FlowGraphEditorSettings.cpp index 9c20e9b00..674427298 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphEditorSettings.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphEditorSettings.cpp @@ -6,7 +6,8 @@ #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowGraphEditorSettings) UFlowGraphEditorSettings::UFlowGraphEditorSettings() - : NodeDoubleClickTarget(EFlowNodeDoubleClickTarget::PrimaryAssetOrNodeDefinition) + : bShowWelcomeWindowOnStartup(true) + , NodeDoubleClickTarget(EFlowNodeDoubleClickTarget::PrimaryAssetOrNodeDefinition) , bShowNodeClass(false) , bShowNodeDescriptionWhilePlaying(true) , bShowAddonDescriptions(true) diff --git a/Source/FlowEditor/Private/Utils/SFlowWelcomeWindow.cpp b/Source/FlowEditor/Private/Utils/SFlowWelcomeWindow.cpp new file mode 100644 index 000000000..a5310a383 --- /dev/null +++ b/Source/FlowEditor/Private/Utils/SFlowWelcomeWindow.cpp @@ -0,0 +1,281 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Utils/SFlowWelcomeWindow.h" + +#include "FlowEditorStyle.h" + +#include "HAL/PlatformProcess.h" +#include "ISettingsModule.h" +#include "Modules/ModuleManager.h" +#include "Styling/AppStyle.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "SFlowWelcomeWindow" + +namespace FlowWelcomeWindow +{ + static const TCHAR* DocsUrl = TEXT("https://mothcocoon.github.io/FlowGraph/"); + static const TCHAR* GitHubUrl = TEXT("https://github.com/MothCocoon/FlowGraph"); + static const TCHAR* FlowGameUrl = TEXT("https://github.com/MothCocoon/FlowGame"); + static const TCHAR* DiscordUrl = TEXT("https://discord.gg/Xmtr6GhbmW"); +} + +void SFlowWelcomeWindow::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(16.f) + [ + SNew(SBox) + .MinDesiredWidth(760.f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(STextBlock) + .Font(FAppStyle::GetFontStyle("HeadingMedium")) + .Text(LOCTEXT("WelcomeTitle", "Welcome to Flow Graph")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 8.f, 0.f, 0.f) + [ + SNew(STextBlock) + .AutoWrapText(true) + .Text(LOCTEXT("WelcomeDescription", "Thanks for enabling the Flow Graph plugin. Use the resources below to quickly get started.")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 14.f, 0.f, 0.f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(12.f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(STextBlock) + .Font(FAppStyle::GetFontStyle("NormalFontBold")) + .ColorAndOpacity(FLinearColor(1.f, 0.75f, 0.1f, 1.f)) + .Text(LOCTEXT("ImportantTitle", "Important Setup")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 8.f, 0.f, 0.f) + [ + SNew(STextBlock) + .AutoWrapText(true) + .Text(LOCTEXT("ImportantBullet1", "- Open Project Settings.")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 4.f, 0.f, 0.f) + [ + SNew(STextBlock) + .AutoWrapText(true) + .Text(LOCTEXT("ImportantBullet2", "- Set World Settings class to Flow World Settings.")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 4.f, 0.f, 0.f) + [ + SNew(STextBlock) + .AutoWrapText(true) + .Text(LOCTEXT("ImportantBullet3", "- Restart the editor.")) + ] + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 10.f, 0.f, 0.f) + [ + SNew(SButton) + .OnClicked(this, &SFlowWelcomeWindow::OnOpenProjectSettings) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FAppStyle::Get().GetBrush("Icons.Settings")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(6.f, 0.f, 0.f, 0.f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("OpenProjectSettingsButton", "Open Project Settings")) + ] + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 16.f, 0.f, 0.f) + [ + SNew(STextBlock) + .Text(LOCTEXT("ResourcesTitle", "Resources")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.f, 8.f, 0.f, 0.f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(10.f) + [ + SNew(SUniformGridPanel) + .SlotPadding(FMargin(8.f)) + .MinDesiredSlotWidth(200.f) + .MinDesiredSlotHeight(34.f) + + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .OnClicked(this, &SFlowWelcomeWindow::OnOpenDocs) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FFlowEditorStyle::GetBrush("FlowWelcome.Docs")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(6.f, 0.f, 0.f, 0.f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("DocsButton", "Docs")) + ] + ] + ] + + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .OnClicked(this, &SFlowWelcomeWindow::OnOpenGitHub) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FFlowEditorStyle::GetBrush("FlowWelcome.GitHub")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(6.f, 0.f, 0.f, 0.f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("FlowGraphButton", "Flow Graph")) + ] + ] + ] + + + SUniformGridPanel::Slot(0, 1) + [ + SNew(SButton) + .OnClicked(this, &SFlowWelcomeWindow::OnOpenFlowGame) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FFlowEditorStyle::GetBrush("FlowWelcome.GitHub")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(6.f, 0.f, 0.f, 0.f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("FlowGameButton", "Flow Game")) + ] + ] + ] + + + SUniformGridPanel::Slot(1, 1) + [ + SNew(SButton) + .OnClicked(this, &SFlowWelcomeWindow::OnOpenDiscord) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FFlowEditorStyle::GetBrush("FlowWelcome.Discord")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(6.f, 0.f, 0.f, 0.f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("DiscordButton", "Discord")) + ] + ] + ] + ] + ] + ] + ] + ]; +} + +void SFlowWelcomeWindow::OpenExternalLink(const TCHAR* Url) +{ + FPlatformProcess::LaunchURL(Url, nullptr, nullptr); +} + +FReply SFlowWelcomeWindow::OnOpenDocs() const +{ + OpenExternalLink(FlowWelcomeWindow::DocsUrl); + return FReply::Handled(); +} + +FReply SFlowWelcomeWindow::OnOpenGitHub() const +{ + OpenExternalLink(FlowWelcomeWindow::GitHubUrl); + return FReply::Handled(); +} + +FReply SFlowWelcomeWindow::OnOpenFlowGame() const +{ + OpenExternalLink(FlowWelcomeWindow::FlowGameUrl); + return FReply::Handled(); +} + +FReply SFlowWelcomeWindow::OnOpenDiscord() const +{ + OpenExternalLink(FlowWelcomeWindow::DiscordUrl); + return FReply::Handled(); +} + +FReply SFlowWelcomeWindow::OnOpenProjectSettings() const +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "Engine", "General"); + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index df7940396..3326cecb6 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -59,8 +59,12 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen public: FDelegateHandle FlowTrackCreateEditorHandle; FDelegateHandle ModulesChangedHandle; + FDelegateHandle PostEngineInitHandle; private: + void HandlePostEngineInit(); + void TryOpenWelcomeWindow(); + void ModulesChangesCallback(FName ModuleName, EModuleChangeReason ReasonForChange) const; void RegisterAssetIndexers() const; @@ -71,4 +75,4 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen void OnAssetUpdated(const FAssetData& AssetData); void OnAssetRenamed(const FAssetData& AssetData, const FString& OldObjectPath); -}; \ No newline at end of file +}; diff --git a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h index e1dbdea74..f43284b0f 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h @@ -31,6 +31,10 @@ class FLOWEDITOR_API UFlowGraphEditorSettings : public UDeveloperSettings #if WITH_EDITOR virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endif + + /* Displays the welcome window once, after enabling Flow in a project. */ + UPROPERTY(config, EditAnywhere, Category = "General") + bool bShowWelcomeWindowOnStartup; /* Double-clicking a Flow Node might open relevant asset/code editor. */ UPROPERTY(config, EditAnywhere, Category = "Nodes") diff --git a/Source/FlowEditor/Public/Utils/SFlowWelcomeWindow.h b/Source/FlowEditor/Public/Utils/SFlowWelcomeWindow.h new file mode 100644 index 000000000..a993cf3d1 --- /dev/null +++ b/Source/FlowEditor/Public/Utils/SFlowWelcomeWindow.h @@ -0,0 +1,24 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "Input/Reply.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class FLOWEDITOR_API SFlowWelcomeWindow : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SFlowWelcomeWindow) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + +private: + static void OpenExternalLink(const TCHAR* Url); + + FReply OnOpenDocs() const; + FReply OnOpenGitHub() const; + FReply OnOpenFlowGame() const; + FReply OnOpenDiscord() const; + FReply OnOpenProjectSettings() const; +};