// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "IntroTutorialsPrivatePCH.h" #include "STutorialOverlay.h" #include "STutorialContent.h" #include "EditorTutorial.h" #include "IntroTutorials.h" #include "Kismet2/KismetEditorUtilities.h" #include "LevelEditor.h" #include "BlueprintEditorUtils.h" #include "Guid.h" #include "BlueprintEditor.h" static FName IntroTutorialsModuleName("IntroTutorials"); void STutorialOverlay::Construct(const FArguments& InArgs, UEditorTutorial* InTutorial, FTutorialStage* const InStage) { ParentWindow = InArgs._ParentWindow; bIsStandalone = InArgs._IsStandalone; OnClosed = InArgs._OnClosed; bHasValidContent = InStage != nullptr; OnWidgetWasDrawn = InArgs._OnWidgetWasDrawn; TSharedPtr Overlay; ChildSlot [ SAssignNew(Overlay, SOverlay) +SOverlay::Slot() [ SAssignNew(OverlayCanvas, SCanvas) ] ]; if(InStage != nullptr) { // add non-widget content, if any if(InArgs._AllowNonWidgetContent && InStage->Content.Type != ETutorialContent::None) { Overlay->AddSlot() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STutorialContent, InTutorial, InStage->Content) .OnClosed(InArgs._OnClosed) .OnNextClicked(InArgs._OnNextClicked) .OnHomeClicked(InArgs._OnHomeClicked) .OnBackClicked(InArgs._OnBackClicked) .IsBackEnabled(InArgs._IsBackEnabled) .IsHomeEnabled(InArgs._IsHomeEnabled) .IsNextEnabled(InArgs._IsNextEnabled) .IsStandalone(InArgs._IsStandalone) .WrapTextAt(600.0f) ] ]; } if(InStage->WidgetContent.Num() > 0) { FIntroTutorials& IntroTutorials = FModuleManager::Get().GetModuleChecked("IntroTutorials"); // now add canvas slots for widget-bound content for (const FTutorialWidgetContent& WidgetContent : InStage->WidgetContent) { if (WidgetContent.Content.Type != ETutorialContent::None) { TSharedPtr ContentWidget = SNew(STutorialContent, InTutorial, WidgetContent.Content) .HAlign(WidgetContent.HorizontalAlignment) .VAlign(WidgetContent.VerticalAlignment) .Offset(WidgetContent.Offset) .IsStandalone(bIsStandalone) .OnClosed(InArgs._OnClosed) .OnNextClicked(InArgs._OnNextClicked) .OnHomeClicked(InArgs._OnHomeClicked) .OnBackClicked(InArgs._OnBackClicked) .IsBackEnabled(InArgs._IsBackEnabled) .IsHomeEnabled(InArgs._IsHomeEnabled) .IsNextEnabled(InArgs._IsNextEnabled) .WrapTextAt(WidgetContent.ContentWidth) .Anchor(WidgetContent.WidgetAnchor) .AllowNonWidgetContent(InArgs._AllowNonWidgetContent) .OnWasWidgetDrawn(InArgs._OnWasWidgetDrawn) .NextButtonText(InStage->NextButtonText); PerformWidgetInteractions(InTutorial, WidgetContent); OverlayCanvas->AddSlot() .Position(TAttribute::Create(TAttribute::FGetter::CreateSP(ContentWidget.Get(), &STutorialContent::GetPosition))) .Size(TAttribute::Create(TAttribute::FGetter::CreateSP(ContentWidget.Get(), &STutorialContent::GetSize))) [ ContentWidget.ToSharedRef() ]; OnPaintNamedWidget.AddSP(ContentWidget.Get(), &STutorialContent::HandlePaintNamedWidget); OnResetNamedWidget.AddSP(ContentWidget.Get(), &STutorialContent::HandleResetNamedWidget); OnCacheWindowSize.AddSP(ContentWidget.Get(), &STutorialContent::HandleCacheWindowSize); } } } } } int32 STutorialOverlay::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { if(ParentWindow.IsValid()) { bool bIsPicking = false; FName WidgetNameToHighlight = NAME_None; FIntroTutorials& IntroTutorials = FModuleManager::Get().GetModuleChecked(IntroTutorialsModuleName); if(IntroTutorials.OnIsPicking().IsBound()) { bIsPicking = IntroTutorials.OnIsPicking().Execute(WidgetNameToHighlight); } if(bIsPicking || bHasValidContent) { TSharedPtr PinnedWindow = ParentWindow.Pin(); OnResetNamedWidget.Broadcast(); OnCacheWindowSize.Broadcast(PinnedWindow->GetWindowGeometryInWindow().Size); LayerId = TraverseWidgets(PinnedWindow.ToSharedRef(), PinnedWindow->GetWindowGeometryInWindow(), MyClippingRect, OutDrawElements, LayerId); } } return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); } int32 STutorialOverlay::TraverseWidgets(TSharedRef InWidget, const FGeometry& InGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { bool bIsPicking = false; bool bShouldHighlight = false; bool bShouldDraw = false; FName WidgetNameToHighlight = NAME_None; FIntroTutorials& IntroTutorials = FModuleManager::Get().GetModuleChecked(IntroTutorialsModuleName); if (IntroTutorials.OnValidatePickingCandidate().IsBound()) { bIsPicking = IntroTutorials.OnValidatePickingCandidate().Execute(InWidget,WidgetNameToHighlight,bShouldHighlight); } // First draw the widget if we should TSharedPtr MetaData = InWidget->GetMetaData(); const FName Tag = (MetaData.IsValid() && MetaData->Tag.IsValid()) ? MetaData->Tag : InWidget->GetTag(); if (Tag != NAME_None || MetaData.IsValid()) { // we are a named widget - ask it to draw OnPaintNamedWidget.Broadcast(InWidget, InGeometry); OnWidgetWasDrawn.ExecuteIfBound(Tag); } // Next check and draw the highlight as appropriate if (bIsPicking == true) { // if we are picking, we need to draw an outline here if(WidgetNameToHighlight != NAME_None ) { if(bIsPicking == true) { const FLinearColor Color = bIsPicking && bShouldHighlight ? FLinearColor::Green : FLinearColor::White; FSlateDrawElement::MakeBox(OutDrawElements, LayerId++, InGeometry.ToPaintGeometry(), FCoreStyle::Get().GetBrush(TEXT("Debug.Border")), MyClippingRect, ESlateDrawEffect::None, Color); } } } FArrangedChildren ArrangedChildren(EVisibility::Visible); InWidget->ArrangeChildren(InGeometry, ArrangedChildren); for(int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ChildIndex++) { const FArrangedWidget& ArrangedWidget = ArrangedChildren[ChildIndex]; LayerId = TraverseWidgets(ArrangedWidget.Widget, ArrangedWidget.Geometry, MyClippingRect, OutDrawElements, LayerId); } return LayerId; } void STutorialOverlay::PerformWidgetInteractions(UEditorTutorial* InTutorial, const FTutorialWidgetContent &WidgetContent) { // Open any browser we need too OpenBrowserForWidgetAnchor(InTutorial, WidgetContent); FocusOnAnyBlueprintNodes(WidgetContent); } void STutorialOverlay::OpenBrowserForWidgetAnchor(UEditorTutorial* InTutorial, const FTutorialWidgetContent &WidgetContent) { if(!WidgetContent.WidgetAnchor.TabToFocusOrOpen.IsEmpty()) { IAssetEditorInstance* AssetEditor = nullptr; // Check to see if we can find a blueprint relevant to this node and open the editor for that (Then try to get the tabmanager from that) if(WidgetContent.WidgetAnchor.OuterName.Len() > 0) { // Remove the prefix from the name int32 Space = WidgetContent.WidgetAnchor.OuterName.Find(TEXT(" ")); FString Name = WidgetContent.WidgetAnchor.OuterName.RightChop(Space + 1); TArray AssetPaths; AssetPaths.Add(Name); FAssetEditorManager::Get().OpenEditorsForAssets(AssetPaths); UObject* Blueprint = FindObject(ANY_PACKAGE, *Name); // If we found a blueprint if(Blueprint != nullptr) { IAssetEditorInstance* PotentialAssetEditor = FAssetEditorManager::Get().FindEditorForAsset(Blueprint, false); if(PotentialAssetEditor != nullptr) { AssetEditor = PotentialAssetEditor; } } } // If we haven't found a tab manager, next check the asset editor that we reference in this tutorial, if any if(AssetEditor == nullptr) { // Try looking for the object that this tutorial references (it should already be loaded by this tutorial if it exists). UObject* AssetObject = InTutorial->AssetToUse.ResolveObject(); if(AssetObject != nullptr) { IAssetEditorInstance* PotentialAssetEditor = FAssetEditorManager::Get().FindEditorForAsset(AssetObject, false); if(PotentialAssetEditor != nullptr) { AssetEditor = PotentialAssetEditor; } } } // Invoke any tab if(AssetEditor != nullptr) { AssetEditor->InvokeTab(FTabId(*WidgetContent.WidgetAnchor.TabToFocusOrOpen)); } else { // fallback to trying the main level editor tab manager FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); LevelEditorTabManager->InvokeTab(FName(*WidgetContent.WidgetAnchor.TabToFocusOrOpen)); } } } void STutorialOverlay::FocusOnAnyBlueprintNodes(const FTutorialWidgetContent &WidgetContent) { if (WidgetContent.bAutoFocus == false) { return; } FString Name = WidgetContent.WidgetAnchor.OuterName; int32 NameIndex; Name.FindLastChar(TEXT('.'), NameIndex); FString BlueprintName = Name.RightChop(NameIndex + 1); UBlueprint* Blueprint = FindObject(ANY_PACKAGE, *BlueprintName); // If we find a blueprint if (Blueprint != nullptr) { // Try to grab guid FGuid NodeGuid; FGuid::Parse(WidgetContent.WidgetAnchor.GUIDString, NodeGuid); UEdGraphNode* OutNode = NULL; if (UEdGraphNode* GraphNode = FBlueprintEditorUtils::GetNodeByGUID(Blueprint, NodeGuid)) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(GraphNode, false); } } }