You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Icon is only visible if content is available for the editor in question. Split editor settings into two groups - one is persistent settings and one is progress/state. Tutorials record their dismissed state, so users can permenantly disable the 'nag' for a particular tutorial. Tutorial content now solidifies when the mouse is hovered over it, so it can be made easier to read. Fixed crash on startup if an intro tutorial was displaying rich text. Also fixed crash for TTP# 345094, where a zero-length tutorial was being accessed. [CL 2275934 by Thomas Sarkanen in Main branch]
797 lines
33 KiB
C++
797 lines
33 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "IntroTutorialsPrivatePCH.h"
|
|
#include "SIntroTutorials.h"
|
|
#include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructureModule.h"
|
|
#include "Editor/MainFrame/Public/MainFrame.h"
|
|
#include "Editor/LevelEditor/Public/LevelEditor.h"
|
|
#include "Editor/Kismet/Public/BlueprintEditorModule.h"
|
|
#include "../Plugins/Editor/EpicSurvey/Source/EpicSurvey/Public/IEpicSurveyModule.h"
|
|
#include "Editor/GameProjectGeneration/Public/GameProjectGenerationModule.h"
|
|
#include "Editor/UnrealEd/Public/EditorModes.h"
|
|
#include "Editor/UnrealEd/Public/SourceCodeNavigation.h"
|
|
#include "Editor/Matinee/Public/MatineeModule.h"
|
|
|
|
#include "EngineAnalytics.h"
|
|
#include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h"
|
|
#include "Particles/ParticleSystem.h"
|
|
#include "EditorTutorialSettings.h"
|
|
#include "TutorialStateSettings.h"
|
|
#include "TutorialSettings.h"
|
|
#include "Settings.h"
|
|
#include "EditorTutorial.h"
|
|
#include "SEditorTutorials.h"
|
|
#include "STutorialsBrowser.h"
|
|
#include "STutorialNavigation.h"
|
|
#include "STutorialOverlay.h"
|
|
#include "TutorialStructCustomization.h"
|
|
#include "EditorTutorialDetailsCustomization.h"
|
|
#include "STutorialRoot.h"
|
|
#include "STutorialButton.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "IntroTutorials"
|
|
|
|
IMPLEMENT_MODULE(FIntroTutorials, IntroTutorials)
|
|
|
|
const FName FIntroTutorials::IntroTutorialTabName(TEXT("IntroTutorial"));
|
|
|
|
const FString FIntroTutorials::IntroTutorialConfigSection(TEXT("IntroTutorials"));
|
|
const FString FIntroTutorials::DisableTutorialsSettingName(TEXT("DisableAllTutorials"));
|
|
const FString FIntroTutorials::InEditorTutorialPath(TEXT("Shared/Tutorials/inEditorTutorial"));
|
|
const FString FIntroTutorials::WelcomeTutorialPath(TEXT("Shared/Tutorials/UE4Welcome"));
|
|
const FString FIntroTutorials::InEditorGamifiedTutorialPath(TEXT("Shared/Tutorials/inEditorGamifiedTutorial"));
|
|
const FString FIntroTutorials::HomePath(TEXT("Shared/Tutorials"));
|
|
const FString FIntroTutorials::BlueprintHomePath(TEXT("Shared/Tutorials/TemplateTutorials/TemplateOverview"));
|
|
const FString FIntroTutorials::TemplateOverviewPath(TEXT("Shared/Tutorials/UE4Welcome"));
|
|
|
|
const FWelcomeTutorialProperties FIntroTutorials::UE4WelcomeTutorial(TEXT("Shared/Tutorials/UE4Welcome"), TEXT("SeenUE4Welcome"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::BlueprintHomeTutorial(TEXT("Shared/Tutorials/InBlueprintEditorTutorial"), TEXT("SeenBlueprintWelcome"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::ClassBlueprintWelcomeTutorial(TEXT("Shared/Tutorials/BlueprintInterfaceTutorial"), TEXT("SeenBlueprintWelcome_Class"), FString("5DCD4D58-9C9F-407F-8388-75D89CBBA7E8"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::MacroLibraryBlueprintWelcomeTutorial(TEXT("Shared/Tutorials/BlueprintMacroLibInterfaceTutorial"), TEXT("SeenBlueprintWelcome_MacroLib"), FString("0D5081E0-F29A-4C35-B4DC-1E5E2825AB54"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::InterfaceBlueprintWelcomeTutorial(TEXT("Shared/Tutorials/BlueprintInterfacesInterfaceTutorial"), TEXT("SeenBlueprintWelcome_Interface"), FString("E86E8C9A-4804-4680-B10F-BDC8E95C7AFD"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::LevelScriptBlueprintWelcomeTutorial(TEXT("Shared/Tutorials/LevelBlueprintInterfaceTutorial"), TEXT("SeenBlueprintWelcome_LevelScript"), FString("B061E309-517D-4916-BFCB-E8104C8F4C35"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::AddCodeToProjectWelcomeTutorial(TEXT("Shared/Tutorials/AddCodeToProjectTutorial"), TEXT("SeenAddCodeToProjectWelcome"), FString("D8D9A7E7-68B3-4CBE-80FE-4B88C47B7524"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::MatineeEditorWelcomeTutorial(TEXT("Shared/Tutorials/InMatineeEditorTutorial"), TEXT("SeenMatineeEditorWelcome"), FString("6439C991-A77B-4B52-953D-3F29B1DD1860"));
|
|
const FWelcomeTutorialProperties FIntroTutorials::TemplateOverview(TemplateOverviewPath, TEXT("SeenTemplateOverview"));
|
|
|
|
FIntroTutorials::FIntroTutorials()
|
|
: CurrentObjectClass(nullptr)
|
|
{
|
|
bDisableTutorials = false;
|
|
bEnablePostTutorialSurveys = false;
|
|
|
|
bDesireResettingTutorialSeenFlagOnLoad = FParse::Param(FCommandLine::Get(), TEXT("tutorials"));
|
|
}
|
|
|
|
/** Generates an analytics name for the given tutorial path string */
|
|
FString FIntroTutorials::AnalyticsEventNameFromTutorialPath(const FString& TutorialPath) const
|
|
{
|
|
// strip off everything but the last folder, supporting both types of slashes
|
|
FString RightStr;
|
|
bool bSplit = TutorialPath.Split( TEXT("/"), NULL, &RightStr, ESearchCase::IgnoreCase, ESearchDir::FromEnd );
|
|
if (!bSplit)
|
|
{
|
|
TutorialPath.Split( TEXT("\\"), NULL, &RightStr, ESearchCase::IgnoreCase, ESearchDir::FromEnd );
|
|
}
|
|
|
|
// then append that to the header
|
|
// e.g. Rocket.Tutorials.ClosedInEditorTutorial
|
|
FString OutStr(TEXT("Rocket.Tutorials.Closed"));
|
|
OutStr += RightStr;
|
|
|
|
return OutStr;
|
|
}
|
|
|
|
TSharedRef<FExtender> FIntroTutorials::AddSummonBlueprintTutorialsMenuExtender(const TSharedRef<FUICommandList> CommandList, const TArray<UObject*> EditingObjects) const
|
|
{
|
|
UObject* PrimaryObject = nullptr;
|
|
if(EditingObjects.Num() > 0)
|
|
{
|
|
PrimaryObject = EditingObjects[0];
|
|
}
|
|
|
|
TSharedRef<FExtender> Extender(new FExtender());
|
|
|
|
Extender->AddMenuExtension(
|
|
"HelpBrowse",
|
|
EExtensionHook::After,
|
|
CommandList,
|
|
FMenuExtensionDelegate::CreateRaw(this, &FIntroTutorials::AddSummonBlueprintTutorialsMenuExtension, PrimaryObject));
|
|
|
|
return Extender;
|
|
}
|
|
|
|
void FIntroTutorials::StartupModule()
|
|
{
|
|
GConfig->GetBool(*IntroTutorialConfigSection, *DisableTutorialsSettingName, bDisableTutorials, GEditorGameAgnosticIni);
|
|
|
|
// This code can run with content commandlets. Slate is not initialized with commandlets and the below code will fail.
|
|
if (!bDisableTutorials && !IsRunningCommandlet())
|
|
{
|
|
// Add tutorial for main frame opening
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
MainFrameModule.OnMainFrameCreationFinished().AddRaw(this, &FIntroTutorials::MainFrameLoad);
|
|
MainFrameModule.OnMainFrameSDKNotInstalled().AddRaw(this, &FIntroTutorials::HandleSDKNotInstalled);
|
|
|
|
// Add menu option for level editor tutorial
|
|
MainMenuExtender = MakeShareable(new FExtender);
|
|
MainMenuExtender->AddMenuExtension("HelpBrowse", EExtensionHook::After, NULL, FMenuExtensionDelegate::CreateRaw(this, &FIntroTutorials::AddSummonTutorialsMenuExtension));
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>( "LevelEditor" );
|
|
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender);
|
|
|
|
// Add menu option to blueprint editor as well
|
|
FBlueprintEditorModule& BPEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>( "Kismet" );
|
|
BPEditorModule.GetMenuExtensibilityManager()->GetExtenderDelegates().Add(FAssetEditorExtender::CreateRaw(this, &FIntroTutorials::AddSummonBlueprintTutorialsMenuExtender));
|
|
|
|
// Add hook for when specific asset editor is opened
|
|
FAssetEditorManager::Get().OnAssetEditorOpened().AddRaw(this, &FIntroTutorials::OnAssetEditorOpened);
|
|
|
|
IMatineeModule::Get().OnMatineeEditorOpened().AddRaw(this, &FIntroTutorials::OnMatineeEditorOpened);
|
|
|
|
// Add hook for when AddToCodeProject dialog window is opened
|
|
FGameProjectGenerationModule::Get().OnAddCodeToProjectDialogOpened().AddRaw(this, &FIntroTutorials::OnAddCodeToProjectDialogOpened);
|
|
|
|
// Add hook for New Project dialog window is opened
|
|
//FGameProjectGenerationModule::Get().OnNewProjectProjectDialogOpened().AddRaw(this, &FIntroTutorials::OnNewProjectDialogOpened);
|
|
|
|
|
|
// Add hook for when editor changes modes (e.g. Place/Paint/Landscape/Foliage)
|
|
GLevelEditorModeTools().OnEditorModeChanged().AddRaw(this, &FIntroTutorials::OnEditorModeChanged);
|
|
|
|
FSourceCodeNavigation::AccessOnCompilerNotFound().AddRaw( this, &FIntroTutorials::HandleCompilerNotFound );
|
|
|
|
// set up some tutorial data
|
|
|
|
// NOTE: we match classes based on an "is-a" relationship, so place more derived classes in the map before base classes so they are encountered first
|
|
AssetEditorTutorialPropertyMap.Add(UTexture::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InTextureEditorTutorial"), TEXT("SeenTextureEditorWelcome"), FString("6CC90D96-03D6-4847-B1DF-EB3018C36C4E")));
|
|
AssetEditorTutorialPropertyMap.Add(UMaterial::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InMaterialEditorTutorial"), TEXT("SeenMaterialEditorWelcome"), FString("2290389E-A428-46C1-B9BD-A7F110CDF83E")));
|
|
AssetEditorTutorialPropertyMap.Add(UAnimBlueprint::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InPersonaAnimBlueprintEditorTutorial"), TEXT("SeenPersonaAnimBlueprintEditorWelcome"), FString("FE5CD131-E4AF-4FE6-9269-12B573B66CA8")));
|
|
AssetEditorTutorialPropertyMap.Add(UBlueprint::StaticClass(), FWelcomeTutorialProperties(FWelcomeTutorialProperties::FWelcomeTutorialPropertiesChooserDelegate::CreateRaw(this, &FIntroTutorials::ChooseBlueprintWelcomeTutorial)));
|
|
AssetEditorTutorialPropertyMap.Add(UStaticMesh::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InStaticMeshEditorTutorial"), TEXT("SeenStaticMeshEditorWelcome"), FString("762FCAD0-9384-4A6B-8D57-92DA771AD890")));
|
|
AssetEditorTutorialPropertyMap.Add(UDestructibleMesh::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InDestructibleMeshEditorTutorial"), TEXT("SeenDestructibleMeshEditorWelcome"), FString("415C385F-C7D3-4CE8-BCC2-F19BB512AF06")));
|
|
AssetEditorTutorialPropertyMap.Add(USkeletalMesh::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InSkeletalMeshEditorTutorial"), TEXT("SeenSkeletalMeshEditorWelcome"), FString("F8BF69A0-391F-4612-B289-B2B2B3F7F428")));
|
|
AssetEditorTutorialPropertyMap.Add(UParticleSystem::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InParticleSystemEditorTutorial"), TEXT("SeenParticleSystemEditorWelcome"), FString("B5ACB9D0-229B-4DA2-A3D6-001A368A48B1")));
|
|
AssetEditorTutorialPropertyMap.Add(USoundCue::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InSoundCueEditorTutorial"), TEXT("SeenSoundCueEditorWelcome"), FString("3C12B4B3-36F8-48D2-A8B0-874CCD5D3891")));
|
|
AssetEditorTutorialPropertyMap.Add(UMaterialInstance::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InMaterialInstanceEditorTutorial"), TEXT("SeenMaterialInstanceEditorWelcome"), FString("A1AAC488-8389-4B71-925D-7A2CFB65F8D4")));
|
|
AssetEditorTutorialPropertyMap.Add(UPhysicsAsset::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InPhATEditorTutorial"), TEXT("SeenPhATEditorWelcome"), FString("B576D8AB-9248-4FE4-80F7-96EDA87BBAD3")));
|
|
|
|
// note these 3 go to the same editor
|
|
AssetEditorTutorialPropertyMap.Add(UAnimMontage::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InPersonaAnimEditorTutorial"), TEXT("SeenPersonaAnimEditorWelcome"), FString("8C7C3772-9135-426C-93A7-5937C5CBEA7B")));
|
|
AssetEditorTutorialPropertyMap.Add(UAnimSequence::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InPersonaAnimEditorTutorial"), TEXT("SeenPersonaAnimEditorWelcome"), FString("8C7C3772-9135-426C-93A7-5937C5CBEA7B")));
|
|
AssetEditorTutorialPropertyMap.Add(UBlendSpace::StaticClass(), FWelcomeTutorialProperties(TEXT("Shared/Tutorials/InPersonaAnimEditorTutorial"), TEXT("SeenPersonaAnimEditorWelcome"), FString("8C7C3772-9135-426C-93A7-5937C5CBEA7B")));
|
|
|
|
// map editor modes to tutorial properties
|
|
//@todo Placement mode appears to be firing erroneously when switching to other modes, want to understand why before enabling this mode
|
|
//EditorModeTutorialPropertyMap.Add(FBuiltinEditorModes::EM_Placement, FWelcomeTutorialProperties(TEXT("Shared/Tutorials/EditorPlacementModeTutorial"), TEXT("SeenEditorPlacementModeWelcome")));
|
|
EditorModeTutorialPropertyMap.Add(FBuiltinEditorModes::EM_Landscape, FWelcomeTutorialProperties(TEXT("Shared/Tutorials/EditorLandscapeModeTutorial"), TEXT("SeenEditorLandscapeModeWelcome"), FString("8B33592A-CCEE-4129-AE3F-77A5B68955CB")));
|
|
EditorModeTutorialPropertyMap.Add(FBuiltinEditorModes::EM_MeshPaint, FWelcomeTutorialProperties(TEXT("Shared/Tutorials/EditorMeshPaintModeTutorial"), TEXT("SeenEditorMeshPaintModeWelcome"), FString("8E36B45D-D249-42E1-ABEE-2ABCBCE8ED26")));
|
|
EditorModeTutorialPropertyMap.Add(FBuiltinEditorModes::EM_Foliage, FWelcomeTutorialProperties(TEXT("Shared/Tutorials/EditorFoliageModeTutorial"), TEXT("SeenEditorFoliageModeWelcome"), FString("9DD00A25-16D5-40D5-BDC4-98DF70CEB0F7")));
|
|
|
|
// init survey map
|
|
{
|
|
for (auto It = AssetEditorTutorialPropertyMap.CreateConstIterator(); It; ++It)
|
|
{
|
|
const FWelcomeTutorialProperties& Props = It.Value();
|
|
TutorialSurveyMap.Add(Props.TutorialPath, Props.SurveyGuid);
|
|
}
|
|
for (auto It = EditorModeTutorialPropertyMap.CreateConstIterator(); It; ++It)
|
|
{
|
|
const FWelcomeTutorialProperties& Props = It.Value();
|
|
TutorialSurveyMap.Add(Props.TutorialPath, Props.SurveyGuid);
|
|
}
|
|
TutorialSurveyMap.Add(ClassBlueprintWelcomeTutorial.TutorialPath, ClassBlueprintWelcomeTutorial.SurveyGuid);
|
|
TutorialSurveyMap.Add(MacroLibraryBlueprintWelcomeTutorial.TutorialPath, MacroLibraryBlueprintWelcomeTutorial.SurveyGuid);
|
|
TutorialSurveyMap.Add(InterfaceBlueprintWelcomeTutorial.TutorialPath, InterfaceBlueprintWelcomeTutorial.SurveyGuid);
|
|
TutorialSurveyMap.Add(LevelScriptBlueprintWelcomeTutorial.TutorialPath, LevelScriptBlueprintWelcomeTutorial.SurveyGuid);
|
|
TutorialSurveyMap.Add(AddCodeToProjectWelcomeTutorial.TutorialPath, AddCodeToProjectWelcomeTutorial.SurveyGuid);
|
|
TutorialSurveyMap.Add(MatineeEditorWelcomeTutorial.TutorialPath, MatineeEditorWelcomeTutorial.SurveyGuid);
|
|
|
|
FGuid SurveyGuid;
|
|
|
|
// set up some paths for surveys and analytics
|
|
SurveyGuid.ParseExact(FString("B6C19629-3172-4924-91FE-AC557F66703C"), EGuidFormats::DigitsWithHyphens, SurveyGuid);
|
|
TutorialSurveyMap.Add(InEditorTutorialPath, SurveyGuid);
|
|
|
|
SurveyGuid.ParseExact(FString("3ADE38A2-9FCA-4BA1-B0A3-F64DDFAB2A0E"), EGuidFormats::DigitsWithHyphens, SurveyGuid);
|
|
TutorialSurveyMap.Add(InEditorGamifiedTutorialPath, SurveyGuid);
|
|
}
|
|
|
|
// maybe reset all the "have I seen this once" flags
|
|
if (bDesireResettingTutorialSeenFlagOnLoad)
|
|
{
|
|
ResetWelcomeTutorials();
|
|
}
|
|
}
|
|
|
|
// Register to display our settings
|
|
ISettingsModule* SettingsModule = ISettingsModule::Get();
|
|
if (SettingsModule != nullptr)
|
|
{
|
|
SettingsModule->RegisterSettings("Editor", "General", "Tutorials",
|
|
LOCTEXT("EditorTutorialSettingsName", "Tutorials"),
|
|
LOCTEXT("EditorTutorialSettingsDescription", "Control what tutorials are available in the Editor."),
|
|
GetMutableDefault<UEditorTutorialSettings>()
|
|
);
|
|
|
|
SettingsModule->RegisterSettings("Project", "Engine", "Tutorials",
|
|
LOCTEXT("TutorialSettingsName", "Tutorials"),
|
|
LOCTEXT("TutorialSettingsDescription", "Control what tutorials are available in this project."),
|
|
GetMutableDefault<UTutorialSettings>()
|
|
);
|
|
}
|
|
|
|
// register details customizations
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
PropertyEditorModule.RegisterCustomPropertyTypeLayout("TutorialContent", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FTutorialContentCustomization::MakeInstance));
|
|
PropertyEditorModule.RegisterCustomPropertyTypeLayout("TutorialContentAnchor", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FTutorialContentAnchorCustomization::MakeInstance));
|
|
PropertyEditorModule.RegisterCustomClassLayout("EditorTutorial", FOnGetDetailCustomizationInstance::CreateStatic(&FEditorTutorialDetailsCustomization::MakeInstance));
|
|
}
|
|
|
|
void FIntroTutorials::ShutdownModule()
|
|
{
|
|
if (!bDisableTutorials && !IsRunningCommandlet())
|
|
{
|
|
FSourceCodeNavigation::AccessOnCompilerNotFound().RemoveAll( this );
|
|
|
|
GLevelEditorModeTools().OnEditorModeChanged().RemoveAll(this);
|
|
}
|
|
|
|
if (BlueprintEditorExtender.IsValid() && FModuleManager::Get().IsModuleLoaded("Kismet"))
|
|
{
|
|
FBlueprintEditorModule& BPEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>("Kismet");
|
|
BPEditorModule.GetMenuExtensibilityManager()->RemoveExtender(BlueprintEditorExtender);
|
|
}
|
|
if (MainMenuExtender.IsValid() && FModuleManager::Get().IsModuleLoaded("LevelEditor"))
|
|
{
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>( "LevelEditor" );
|
|
LevelEditorModule.GetMenuExtensibilityManager()->RemoveExtender(MainMenuExtender);
|
|
}
|
|
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
|
|
{
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
MainFrameModule.OnMainFrameCreationFinished().RemoveAll(this);
|
|
}
|
|
|
|
ISettingsModule* SettingsModule = ISettingsModule::Get();
|
|
if (SettingsModule != nullptr)
|
|
{
|
|
SettingsModule->UnregisterSettings("Editor", "General", "Tutorials");
|
|
SettingsModule->UnregisterSettings("Project", "Engine", "Tutorials");
|
|
}
|
|
|
|
if(FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
|
|
{
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
PropertyEditorModule.UnregisterCustomPropertyTypeLayout("TutorialContent");
|
|
PropertyEditorModule.UnregisterCustomPropertyTypeLayout("TutorialWidgetReference");
|
|
PropertyEditorModule.UnregisterCustomClassLayout("EditorTutorial");
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::AddSummonTutorialsMenuExtension(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.BeginSection("Tutorials", LOCTEXT("TutorialsLabel", "Tutorials"));
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("TutorialsMenuEntryTitle", "Tutorials"),
|
|
LOCTEXT("TutorialsMenuEntryToolTip", "Opens up introductory tutorials covering the basics of using the Unreal Engine 4 Editor."),
|
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tutorials"),
|
|
FUIAction(FExecuteAction::CreateRaw(this, &FIntroTutorials::SummonTutorialHome)));
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
void FIntroTutorials::AddSummonBlueprintTutorialsMenuExtension(FMenuBuilder& MenuBuilder, UObject* PrimaryObject)
|
|
{
|
|
MenuBuilder.BeginSection("Tutorials", LOCTEXT("TutorialsLabel", "Tutorials"));
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("BlueprintMenuEntryTitle", "Blueprint Overview"),
|
|
LOCTEXT("BlueprintMenuEntryToolTip", "Opens up an introductory overview of Blueprints."),
|
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tutorials"),
|
|
FUIAction(FExecuteAction::CreateRaw(this, &FIntroTutorials::SummonBlueprintTutorialHome, PrimaryObject, true)));
|
|
|
|
if(PrimaryObject != nullptr)
|
|
{
|
|
UBlueprint* BP = Cast<UBlueprint>(PrimaryObject);
|
|
if(BP != nullptr)
|
|
{
|
|
UEnum* Enum = FindObject<UEnum>(ANY_PACKAGE, TEXT("EBlueprintType"), true);
|
|
check(Enum);
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::Format(LOCTEXT("BlueprintTutorialsMenuEntryTitle", "{0} Tutorial"), Enum->GetEnumText(BP->BlueprintType)),
|
|
LOCTEXT("BlueprintTutorialsMenuEntryToolTip", "Opens up an introductory tutorial covering this particular part of the Blueprint editor."),
|
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tutorials"),
|
|
FUIAction(FExecuteAction::CreateRaw(this, &FIntroTutorials::SummonBlueprintTutorialHome, PrimaryObject, false)));
|
|
}
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
void FIntroTutorials::MainFrameLoad(TSharedPtr<SWindow> InRootWindow, bool bIsNewProjectWindow)
|
|
{
|
|
if (!bIsNewProjectWindow)
|
|
{
|
|
// Save off pointer to root window so we can parent the tutorial window to it when summoned
|
|
if (InRootWindow.IsValid())
|
|
{
|
|
RootWindow = InRootWindow;
|
|
}
|
|
|
|
// install a root widget for the tutorial overlays to hang off
|
|
if(InRootWindow.IsValid() && !TutorialRoot.IsValid())
|
|
{
|
|
InRootWindow->AddOverlaySlot()
|
|
[
|
|
SAssignNew(TutorialRoot, STutorialRoot)
|
|
];
|
|
}
|
|
|
|
// See if we should show 'welcome' screen
|
|
MaybeOpenWelcomeTutorial(UE4WelcomeTutorial.TutorialPath, UE4WelcomeTutorial.SeenOnceSettingName);
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::SummonTutorialHome()
|
|
{
|
|
if(FParse::Param(FCommandLine::Get(), TEXT("NewTutorials")))
|
|
{
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>( "LevelEditor" );
|
|
SummonTutorialBrowser(LevelEditorModule.GetLevelEditorTab()->GetParentWindow().ToSharedRef());
|
|
}
|
|
else
|
|
{
|
|
CurrentObjectClass = nullptr;
|
|
SummonTutorialWindowForPage(HomePath);
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::SummonBlueprintTutorialHome(UObject* Asset, bool bForceWelcome)
|
|
{
|
|
if(Asset != nullptr)
|
|
{
|
|
CurrentObjectClass = Asset->GetClass();
|
|
}
|
|
|
|
FWelcomeTutorialProperties const* Tutorial = ChooseBlueprintWelcomeTutorial(Asset, bForceWelcome);
|
|
check(Tutorial);
|
|
SummonTutorialWindowForPage(Tutorial->TutorialPath);
|
|
|
|
// make sure we've seen this tutorial
|
|
GConfig->SetBool(*IntroTutorialConfigSection, *Tutorial->SeenOnceSettingName, true, GEditorGameAgnosticIni);
|
|
}
|
|
|
|
void FIntroTutorials::SummonTutorialWindowForPage(const FString& Path)
|
|
{
|
|
TSharedPtr<SWindow> Window;
|
|
|
|
// If window already exists, bring it to the front
|
|
if (TutorialWindow.IsValid())
|
|
{
|
|
Window = TutorialWindow.Pin();
|
|
Window->BringToFront();
|
|
}
|
|
// If not, create a new window
|
|
else
|
|
{
|
|
// Window
|
|
Window = SNew(SWindow)
|
|
.Title(LOCTEXT( "WindowTitle", "Tutorials" ))
|
|
.ClientSize(FVector2D(660.f, 637.f))
|
|
.SupportsMaximize(false)
|
|
.SupportsMinimize(false);
|
|
|
|
TutorialWindow = Window;
|
|
|
|
if(RootWindow.IsValid() && !PLATFORM_MAC)
|
|
{
|
|
FSlateApplication::Get().AddWindowAsNativeChild(Window.ToSharedRef(), RootWindow.Pin().ToSharedRef());
|
|
}
|
|
else
|
|
{
|
|
FSlateApplication::Get().AddWindow(Window.ToSharedRef());
|
|
}
|
|
|
|
// tutorial Widget
|
|
TSharedRef<SIntroTutorials> TutorialWidgetRef =
|
|
SNew(SIntroTutorials)
|
|
.ParentWindow(Window)
|
|
.HomeButtonVisibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateRaw(this, &FIntroTutorials::GetHomeButtonVisibility)))
|
|
.OnGotoNextTutorial(FOnGotoNextTutorial::CreateRaw(this, &FIntroTutorials::HandleGotoNextTutorial));
|
|
|
|
Window->SetContent(TutorialWidgetRef);
|
|
|
|
Window->SetOnWindowClosed( FOnWindowClosed::CreateRaw(this, &FIntroTutorials::OnTutorialWindowClosed) );
|
|
TutorialWidgetRef->SetOnGoHome( FOnGoHome::CreateRaw(this, &FIntroTutorials::OnTutorialDismissed) );
|
|
|
|
TutorialWidget = TutorialWidgetRef;
|
|
|
|
Window->BringToFront();
|
|
}
|
|
|
|
// Change page to desired path
|
|
if(TutorialWidget.IsValid())
|
|
{
|
|
TutorialWidget.Pin()->ChangePage(Path);
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::OnTutorialWindowClosed(const TSharedRef<SWindow>& Window)
|
|
{
|
|
OnTutorialDismissed();
|
|
}
|
|
|
|
void FIntroTutorials::OnTutorialDismissed() const
|
|
{
|
|
TSharedPtr<SIntroTutorials> WidgetPtr = TutorialWidget.Pin();
|
|
if (WidgetPtr.IsValid())
|
|
{
|
|
FString const CurrentPagePath = WidgetPtr->GetCurrentPagePath();
|
|
|
|
// submit analytics data
|
|
if( FEngineAnalytics::IsAvailable() )
|
|
{
|
|
// prepare and send analytics data
|
|
bool const bQuitOnWelcomeScreen = (CurrentPagePath == WelcomeTutorialPath);
|
|
|
|
FString const CurrentExcerptTitle = bQuitOnWelcomeScreen ? TEXT("Welcome Screen") : WidgetPtr->GetCurrentExcerptIdentifierName();
|
|
int32 const CurrentExcerptIndex = bQuitOnWelcomeScreen ? -1 : WidgetPtr->GetCurrentExcerptIndex();
|
|
float const CurrentPageElapsedTime = bQuitOnWelcomeScreen ? 0.f : WidgetPtr->GetCurrentPageElapsedTime();
|
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes;
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("LastExcerptIndex"), CurrentExcerptIndex));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("LastExcerptTitle"), CurrentExcerptTitle));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("TimeSpentInTutorial"), CurrentPageElapsedTime));
|
|
|
|
FString AnalyticsEventName = AnalyticsEventNameFromTutorialPath(CurrentPagePath);
|
|
|
|
FEngineAnalytics::GetProvider().RecordEvent( AnalyticsEventName, EventAttributes );
|
|
}
|
|
|
|
// kick off survey, but not if it was dismissed immediately
|
|
if ( bEnablePostTutorialSurveys && IEpicSurveyModule::IsAvailable() && (WidgetPtr->GetCurrentExcerptIndex() > 0) )
|
|
{
|
|
FGuid const* const SurveyID = TutorialSurveyMap.Find(CurrentPagePath);
|
|
if (SurveyID && !SurveyID->IsValid())
|
|
{
|
|
// launch end-of-tutorial survey if one is desired
|
|
IEpicSurveyModule::Get().PromptSurvey(*SurveyID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::OnAssetEditorOpened(UObject* Asset)
|
|
{
|
|
if(Asset != nullptr)
|
|
{
|
|
FWelcomeTutorialProperties const* const FoundProps = FindAssetEditorTutorialProperties(Asset->GetClass());
|
|
if (FoundProps)
|
|
{
|
|
// run delegate if it exists
|
|
FWelcomeTutorialProperties const* const PropsToUse = FoundProps->ChooserDelegate.IsBound()
|
|
? FoundProps->ChooserDelegate.Execute(Asset)
|
|
: FoundProps;
|
|
|
|
if (PropsToUse)
|
|
{
|
|
if(MaybeOpenWelcomeTutorial(PropsToUse->TutorialPath, PropsToUse->SeenOnceSettingName))
|
|
{
|
|
CurrentObjectClass = Asset->GetClass();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FWelcomeTutorialProperties const* FIntroTutorials::ChooseBlueprintWelcomeTutorial(UObject* BlueprintObject)
|
|
{
|
|
return ChooseBlueprintWelcomeTutorial(BlueprintObject, true);
|
|
}
|
|
|
|
FWelcomeTutorialProperties const* FIntroTutorials::ChooseBlueprintWelcomeTutorial(UObject* BlueprintObject, bool bForceWelcome)
|
|
{
|
|
TutorialChainMap.Empty();
|
|
|
|
UBlueprint* BP = Cast<UBlueprint>(BlueprintObject);
|
|
if (BP)
|
|
{
|
|
bool bSeenWelcome = false;
|
|
GConfig->GetBool(*IntroTutorialConfigSection, *BlueprintHomeTutorial.SeenOnceSettingName, bSeenWelcome, GEditorGameAgnosticIni);
|
|
if(!bSeenWelcome || bForceWelcome)
|
|
{
|
|
switch (BP->BlueprintType)
|
|
{
|
|
case BPTYPE_Normal:
|
|
TutorialChainMap.Add(BlueprintHomePath, ClassBlueprintWelcomeTutorial.TutorialPath);
|
|
break;
|
|
case BPTYPE_MacroLibrary:
|
|
TutorialChainMap.Add(BlueprintHomePath, MacroLibraryBlueprintWelcomeTutorial.TutorialPath);
|
|
break;
|
|
case BPTYPE_Interface:
|
|
TutorialChainMap.Add(BlueprintHomePath, InterfaceBlueprintWelcomeTutorial.TutorialPath);
|
|
break;
|
|
case BPTYPE_LevelScript:
|
|
TutorialChainMap.Add(BlueprintHomePath, LevelScriptBlueprintWelcomeTutorial.TutorialPath);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (BP->BlueprintType)
|
|
{
|
|
case BPTYPE_Normal:
|
|
return &ClassBlueprintWelcomeTutorial;
|
|
case BPTYPE_MacroLibrary:
|
|
return &MacroLibraryBlueprintWelcomeTutorial;
|
|
case BPTYPE_Interface:
|
|
return &InterfaceBlueprintWelcomeTutorial;
|
|
case BPTYPE_LevelScript:
|
|
return &LevelScriptBlueprintWelcomeTutorial;
|
|
}
|
|
}
|
|
}
|
|
|
|
return &BlueprintHomeTutorial;
|
|
}
|
|
|
|
FWelcomeTutorialProperties const* FIntroTutorials::FindAssetEditorTutorialProperties(UClass const* Class) const
|
|
{
|
|
for (auto It = AssetEditorTutorialPropertyMap.CreateConstIterator(); It; ++It)
|
|
{
|
|
UClass* const KeyClass = It.Key();
|
|
if (Class->IsChildOf(KeyClass))
|
|
{
|
|
return &It.Value();
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool FIntroTutorials::MaybeOpenWelcomeTutorial(const FString& TutorialPath, const FString& ConfigSettingName)
|
|
{
|
|
if(FParse::Param(FCommandLine::Get(), TEXT("NewTutorials")))
|
|
{
|
|
// try editor startup tutorial
|
|
TSubclassOf<UEditorTutorial> EditorStartupTutorialClass = LoadClass<UEditorTutorial>(NULL, *GetDefault<UEditorTutorialSettings>()->StartupTutorial.AssetLongPathname, NULL, LOAD_None, NULL);
|
|
if(EditorStartupTutorialClass != nullptr)
|
|
{
|
|
UEditorTutorial* Tutorial = EditorStartupTutorialClass->GetDefaultObject<UEditorTutorial>();
|
|
if (!GetDefault<UTutorialStateSettings>()->HaveSeenTutorial(Tutorial))
|
|
{
|
|
LaunchTutorial(Tutorial);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Try project startup tutorial
|
|
TSubclassOf<UEditorTutorial> ProjectStartupTutorialClass = LoadClass<UEditorTutorial>(NULL, *GetDefault<UTutorialSettings>()->StartupTutorial.AssetLongPathname, NULL, LOAD_None, NULL);
|
|
if(ProjectStartupTutorialClass != nullptr)
|
|
{
|
|
UEditorTutorial* Tutorial = ProjectStartupTutorialClass->GetDefaultObject<UEditorTutorial>();
|
|
if (!GetDefault<UTutorialStateSettings>()->HaveSeenTutorial(Tutorial))
|
|
{
|
|
LaunchTutorial(Tutorial);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// don't open if viewing any tutorial other than the index
|
|
if (TutorialWidget.IsValid() && (TutorialWidget.Pin()->GetCurrentPagePath() != HomePath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bSeenWelcome = false;
|
|
GConfig->GetBool(*IntroTutorialConfigSection, *ConfigSettingName, bSeenWelcome, GEditorGameAgnosticIni);
|
|
|
|
if (!bSeenWelcome)
|
|
{
|
|
bool const bPageExists = IDocumentation::Get()->PageExists(*TutorialPath);
|
|
if (bPageExists)
|
|
{
|
|
SummonTutorialWindowForPage(*TutorialPath);
|
|
|
|
// Tell ini file that we've seen this now
|
|
GConfig->SetBool(*IntroTutorialConfigSection, *ConfigSettingName, true, GEditorGameAgnosticIni);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FIntroTutorials::MaybeOpenWelcomeTutorial(const FWelcomeTutorialProperties& TutorialProperties)
|
|
{
|
|
return MaybeOpenWelcomeTutorial(TutorialProperties.TutorialPath, TutorialProperties.SeenOnceSettingName);
|
|
}
|
|
|
|
template< typename KeyType >
|
|
void FIntroTutorials::ResetTutorialPropertyMap(TMap<KeyType, FWelcomeTutorialProperties> Map) const
|
|
{
|
|
for (auto It = Map.CreateConstIterator(); It; ++It)
|
|
{
|
|
ResetTutorial(It.Value());
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::ResetWelcomeTutorials() const
|
|
{
|
|
ResetTutorialPropertyMap(AssetEditorTutorialPropertyMap);
|
|
ResetTutorialPropertyMap(EditorModeTutorialPropertyMap);
|
|
|
|
ResetTutorial(UE4WelcomeTutorial);
|
|
ResetTutorial(BlueprintHomeTutorial);
|
|
ResetTutorial(ClassBlueprintWelcomeTutorial);
|
|
ResetTutorial(MacroLibraryBlueprintWelcomeTutorial);
|
|
ResetTutorial(InterfaceBlueprintWelcomeTutorial);
|
|
ResetTutorial(LevelScriptBlueprintWelcomeTutorial);
|
|
ResetTutorial(AddCodeToProjectWelcomeTutorial);
|
|
ResetTutorial(MatineeEditorWelcomeTutorial);
|
|
ResetTutorial(TemplateOverview);
|
|
}
|
|
|
|
void FIntroTutorials::OnAddCodeToProjectDialogOpened()
|
|
{
|
|
MaybeOpenWelcomeTutorial(AddCodeToProjectWelcomeTutorial);
|
|
}
|
|
|
|
void FIntroTutorials::OnNewProjectDialogOpened()
|
|
{
|
|
MaybeOpenWelcomeTutorial(TemplateOverview);
|
|
}
|
|
|
|
void FIntroTutorials::OnMatineeEditorOpened()
|
|
{
|
|
MaybeOpenWelcomeTutorial(MatineeEditorWelcomeTutorial);
|
|
}
|
|
|
|
void FIntroTutorials::OnEditorModeChanged(FEdMode* Mode, bool bEnteringMode)
|
|
{
|
|
// editor-mode changed events can fire before the MainFrameLoad event, so wait until *after* the welcome tutorial to start showing editor-mode tutorials
|
|
// this avoids erroneously suppressing a mode tutorial because it opened but was then immediately replaced by the UE4 welcome tutorial
|
|
if (!HasSeenTutorial(UE4WelcomeTutorial))
|
|
return;
|
|
|
|
// only show tutorials when entering an editor mode, not when leaving it
|
|
if (bEnteringMode)
|
|
{
|
|
FWelcomeTutorialProperties const* const FoundProp = EditorModeTutorialPropertyMap.Find(Mode->GetID());
|
|
if (FoundProp)
|
|
{
|
|
MaybeOpenWelcomeTutorial(FoundProp->TutorialPath, FoundProp->SeenOnceSettingName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::HandleCompilerNotFound()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
SummonTutorialWindowForPage( TEXT( "Shared/Tutorials/InstallingVisualStudioTutorial" ) );
|
|
#elif PLATFORM_MAC
|
|
SummonTutorialWindowForPage( TEXT( "Shared/Tutorials/InstallingXCodeTutorial" ) );
|
|
#else
|
|
STUBBED("FIntroTutorials::HandleCompilerNotFound");
|
|
#endif
|
|
}
|
|
|
|
void FIntroTutorials::HandleSDKNotInstalled(const FString& PlatformName, const FString& DocLink)
|
|
{
|
|
SummonTutorialWindowForPage(DocLink);
|
|
}
|
|
|
|
bool FIntroTutorials::HasSeenTutorial( const FWelcomeTutorialProperties& TutProps ) const
|
|
{
|
|
bool bSeenTutorial = false;
|
|
GConfig->GetBool(*IntroTutorialConfigSection, *TutProps.SeenOnceSettingName, bSeenTutorial, GEditorGameAgnosticIni);
|
|
return bSeenTutorial;
|
|
}
|
|
|
|
void FIntroTutorials::ResetTutorial(const FWelcomeTutorialProperties& TutProps) const
|
|
{
|
|
FString SettingName = TutProps.SeenOnceSettingName;
|
|
if (!SettingName.IsEmpty())
|
|
{
|
|
GConfig->SetBool(*IntroTutorialConfigSection, *SettingName, false, GEditorGameAgnosticIni);
|
|
}
|
|
}
|
|
|
|
EVisibility FIntroTutorials::GetHomeButtonVisibility() const
|
|
{
|
|
if(CurrentObjectClass != nullptr)
|
|
{
|
|
return CurrentObjectClass->IsChildOf(UBlueprint::StaticClass()) ? EVisibility::Hidden : EVisibility::Visible;
|
|
}
|
|
|
|
return EVisibility::Visible;
|
|
}
|
|
|
|
FString FIntroTutorials::HandleGotoNextTutorial(const FString& InCurrentPage) const
|
|
{
|
|
const FString* NextTutorial = TutorialChainMap.Find(InCurrentPage);
|
|
if(NextTutorial != nullptr)
|
|
{
|
|
return *NextTutorial;
|
|
}
|
|
|
|
return FString();
|
|
}
|
|
|
|
void FIntroTutorials::RegisterTutorialForAssetEditor(UClass* AssetClass, const FString& TutorialDocPath, const FString& TutorialHasBeenSeenSettingName, const FString& SurveyGUIDString)
|
|
{
|
|
FWelcomeTutorialProperties TutorialData(TutorialDocPath, TutorialHasBeenSeenSettingName, SurveyGUIDString);
|
|
|
|
if (bDesireResettingTutorialSeenFlagOnLoad)
|
|
{
|
|
ResetTutorial(TutorialData);
|
|
}
|
|
|
|
TutorialSurveyMap.Add(TutorialDocPath, TutorialData.SurveyGuid);
|
|
|
|
AssetEditorTutorialPropertyMap.Add(AssetClass, TutorialData);
|
|
}
|
|
|
|
void FIntroTutorials::UnregisterTutorialForAssetEditor(UClass* AssetClass)
|
|
{
|
|
const FWelcomeTutorialProperties& TutorialData = AssetEditorTutorialPropertyMap.FindChecked(AssetClass);
|
|
TutorialSurveyMap.Remove(TutorialData.TutorialPath);
|
|
AssetEditorTutorialPropertyMap.Remove(AssetClass);
|
|
}
|
|
|
|
FOnIsPicking& FIntroTutorials::OnIsPicking()
|
|
{
|
|
return OnIsPickingDelegate;
|
|
}
|
|
|
|
void FIntroTutorials::SummonTutorialBrowser(TSharedRef<SWindow> InWindow, const FString& InFilter)
|
|
{
|
|
if(TutorialRoot.IsValid())
|
|
{
|
|
TutorialRoot->SummonTutorialBrowser(InWindow, InFilter);
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::LaunchTutorial(UEditorTutorial* InTutorial, bool bInRestart, TWeakPtr<SWindow> InNavigationWindow, FSimpleDelegate OnTutorialClosed, FSimpleDelegate OnTutorialExited)
|
|
{
|
|
if(TutorialRoot.IsValid())
|
|
{
|
|
TutorialRoot->LaunchTutorial(InTutorial, bInRestart, InNavigationWindow, OnTutorialClosed, OnTutorialExited);
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::CloseAllTutorialContent()
|
|
{
|
|
if (TutorialRoot.IsValid())
|
|
{
|
|
TutorialRoot->CloseAllTutorialContent();
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::GoToPreviousStage()
|
|
{
|
|
if (TutorialRoot.IsValid())
|
|
{
|
|
TutorialRoot->GoToPreviousStage();
|
|
}
|
|
}
|
|
|
|
void FIntroTutorials::GoToNextStage(TWeakPtr<SWindow> InNavigationWindow)
|
|
{
|
|
if (TutorialRoot.IsValid())
|
|
{
|
|
TutorialRoot->GoToNextStage(InNavigationWindow);
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> FIntroTutorials::CreateTutorialsWidget(FName InContext, TWeakPtr<SWindow> InContextWindow) const
|
|
{
|
|
return SNew(STutorialButton)
|
|
.Context(InContext)
|
|
.ContextWindow(InContextWindow);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |