Files
UnrealEngineUWP/Engine/Source/Editor/IntroTutorials/Private/SIntroTutorials.cpp
Michael Noland b6e9810498 Editor: Convert IntroTutorials to a module so other plugins can reference it
[CL 2116088 by Michael Noland in Main branch]
2014-06-24 20:14:23 -04:00

694 lines
18 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "IntroTutorialsPrivatePCH.h"
#include "SIntroTutorials.h"
#include "IDocumentation.h"
#include "SoundDefinitions.h"
#define LOCTEXT_NAMESPACE "IntroTutorials"
const FString SIntroTutorials::HomePath = TEXT("Shared/Tutorials");
/**
* A widget used to control the display of the dynamic titles in a tutorial.
*/
class STutorialStageTicker : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( STutorialStageTicker ) {}
/** The current excerpt to display */
SLATE_ATTRIBUTE( FText, CurrentExcerpt )
/** The progress string for the current excerpt (e.g. 4/16) */
SLATE_ATTRIBUTE( FText, CurrentProgressText )
SLATE_END_ARGS()
void Construct( const FArguments& InArgs )
{
ChildSlot
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Tutorials.CurrentExcerpt"))
]
+SHorizontalBox::Slot()
.FillWidth(1.0f)
.AutoWidth()
[
SNew(STextBlock)
.Text(InArgs._CurrentExcerpt)
.TextStyle(FEditorStyle::Get(), "Tutorials.CurrentExcerpt")
]
];
}
};
/**
* A widget used for consistency of appearance of tutorial navigation buttons
*/
class STutorialNavigationButton : public SButton
{
public:
SLATE_BEGIN_ARGS( STutorialNavigationButton ) {}
/** The image to display */
SLATE_ARGUMENT( FName, ImageName )
/** The text to display */
SLATE_ATTRIBUTE( FText, Text )
/** Called when the button is clicked */
SLATE_EVENT( FOnClicked, OnClicked )
/** Whether the image should be displayed on the left or right */
SLATE_ARGUMENT( bool, ImageOnLeft )
/** Whether the image should be displayed on the left or right */
SLATE_ARGUMENT(bool, ImageOnly)
SLATE_END_ARGS()
void Construct( const FArguments& InArgs )
{
SButton::Construct(SButton::FArguments()
.ButtonStyle( FEditorStyle::Get(), "HoverHintOnly" )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ToolTipText(InArgs._ToolTipText)
.OnClicked(InArgs._OnClicked)
[
MakeContent(InArgs._ImageOnLeft, InArgs._ImageOnly, InArgs._Text, InArgs._ImageName)
]);
}
private:
/** Make the button content */
TSharedRef<SWidget> MakeContent(bool bImageOnLeft, bool bImageOnly, const TAttribute<FText>& InText, const FName& InImageName)
{
if (bImageOnly)
{
return MakeImage(InImageName);
}
else if(bImageOnLeft)
{
return SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Padding(FMargin(4.f, 0.f))
[
MakeImage(InImageName)
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
MakeText(InText)
];
}
else
{
return SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.Padding( FMargin(4.f,0.f) )
.VAlign(VAlign_Center)
[
MakeText(InText)
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
MakeImage(InImageName)
];
}
}
/** Make the image content */
TSharedRef<SWidget> MakeImage( const FName& InImageName )
{
return SNew(SImage)
.Image(FEditorStyle::GetBrush(InImageName))
.ColorAndOpacity(this, &STutorialNavigationButton::GetColor);
}
/** Make the text content */
TSharedRef<SWidget> MakeText(const TAttribute<FText>& InText)
{
return SNew(STextBlock)
.Text(InText)
.TextStyle(FEditorStyle::Get(), "Tutorials.NavigationButtons")
.ColorAndOpacity(this, &STutorialNavigationButton::GetColor);
}
/** Get the color according to state */
FSlateColor GetColor() const
{
if(IsEnabled())
{
return IsHovered() ? FEditorStyle::GetSlateColor("Tutorials.ButtonHighlightColor") : FEditorStyle::GetColor("Tutorials.ButtonColor");
}
// The default disabled effect tries to de-saturate the color, but the color
// is already fairly desaturated, so we need to supply a different color when the
// button is disabled for it to show up.
return FEditorStyle::GetSlateColor("Tutorials.ButtonDisabledColor");
}
};
void SIntroTutorials::Construct( const FArguments& Args )
{
ParentWindowPtr = Args._ParentWindow;
HomeButtonVisibility = Args._HomeButtonVisibility;
OnGotoNextTutorial = Args._OnGotoNextTutorial;
DialogueAudioComponent = NULL;
bCurrentSectionIsInteractive = false;
CurrentExcerptIndex = 0;
CurrentPageStartTime = 0.0;
ParserConfiguration = FParserConfiguration::Create();
ParserConfiguration->OnNavigate = FOnNavigate::CreateRaw(this, &SIntroTutorials::ChangePage);
ChildSlot
[
SNew(SBorder)
.Padding(5)
.BorderImage(this, &SIntroTutorials::GetContentAreaBackground)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
[
SNew(SVerticalBox)
.Visibility(this, &SIntroTutorials::GetContentVisibility)
+SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STutorialStageTicker)
.CurrentExcerpt(this, &SIntroTutorials::GetCurrentExcerptTitle)
.CurrentProgressText(this, &SIntroTutorials::GetProgressText)
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 8.0f))
[
SNew(SProgressBar)
.Style( FEditorStyle::Get(), "Tutorials.ProgressBar")
.Percent(this, &SIntroTutorials::GetProgress)
.FillColorAndOpacity( FLinearColor(0.19f, 0.33f, 0.72f) )
]
+SVerticalBox::Slot()
.FillHeight(1.0f)
[
SAssignNew(ContentArea, SBorder)
.Padding(6.f)
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.f, 6.f, 0.f, 0.f))
[
SNew(SSeparator)
.SeparatorImage(FEditorStyle::GetBrush("Tutorials.Separator"))
]
+SVerticalBox::Slot()
.Padding(FMargin(0.0f, 8.0f, 0.0f, 0.0f))
.AutoHeight()
[
SNew(SHorizontalBox)
.Visibility(this, &SIntroTutorials::GetNavigationVisibility)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(STutorialNavigationButton)
.ToolTipText(LOCTEXT("PreviousButtonTooltip", "Go back to the previous tutorial page."))
.OnClicked(this, &SIntroTutorials::OnPreviousClicked)
.IsEnabled(this, &SIntroTutorials::OnPreviousIsEnabled)
.ImageName("Tutorials.Back")
.Text(this, &SIntroTutorials::GetBackButtonText)
.ImageOnLeft(true)
.ImageOnly(false)
.Visibility(this, &SIntroTutorials::GetBackButtonVisibility)
]
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(STutorialNavigationButton)
.ToolTipText(LOCTEXT("HomeButtonTooltip", "Go back to the tutorial index."))
.OnClicked(this, &SIntroTutorials::OnHomeClicked)
.ImageName("Tutorials.Home")
.ImageOnLeft(true)
.ImageOnly(true)
.Visibility(HomeButtonVisibility)
]
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(STutorialNavigationButton)
.ToolTipText(LOCTEXT("NextButtonTooltip", "Go to the next tutorial page."))
.OnClicked(this, &SIntroTutorials::OnNextClicked)
.IsEnabled(this, &SIntroTutorials::OnNextIsEnabled)
.ImageName("Tutorials.Next")
.Text(this, &SIntroTutorials::GetNextButtonText)
.ImageOnLeft(false)
.ImageOnly(false)
.Visibility(this, &SIntroTutorials::GetNextButtonVisibility)
]
]
]
]
+SVerticalBox::Slot()
[
SAssignNew(HomeContentArea, SBorder)
.Visibility(this, &SIntroTutorials::GetHomeContentVisibility)
.Padding(FMargin(0,0,0,5))
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
]
]
];
// Set the documentation style to what we want to use in the tutorials */
DocumentationStyle
.ContentStyle(TEXT("Tutorials.Content"))
.BoldContentStyle(TEXT("Tutorials.BoldContent"))
.NumberedContentStyle(TEXT("Tutorials.NumberedContent"))
.Header1Style(TEXT("Tutorials.Header1"))
.Header2Style(TEXT("Tutorials.Header2"))
.HyperlinkButtonStyle(TEXT("Tutorials.Hyperlink.Button"))
.HyperlinkTextStyle(TEXT("Tutorials.Hyperlink.Text"))
.SeparatorStyle(TEXT("Tutorials.Separator"));
InteractiveTutorials = MakeShareable(new FInteractiveTutorials(FSimpleDelegate::CreateSP(this, &SIntroTutorials::TriggerCompleted)));
InteractiveTutorials->SetupEditorHooks();
ChangePage(HomePath);
}
bool SIntroTutorials::IsHomeStyle() const
{
return InteractiveTutorials->GetCurrentTutorialStyle() == ETutorialStyle::Home;
}
void SIntroTutorials::ChangePage(const FString& Path)
{
bool const bPageExists = IDocumentation::Get()->PageExists(*Path);
if (bPageExists)
{
TSharedPtr<IDocumentationPage> NewPage = IDocumentation::Get()->GetPage(Path, ParserConfiguration, DocumentationStyle);
if (NewPage.IsValid() && (NewPage->GetNumExcerpts() > 0))
{
InteractiveTutorials->SetCurrentTutorial(Path);
CurrentPage = NewPage;
CurrentPage->GetExcerpts(Excerpts);
SetCurrentExcerpt(0);
CurrentPagePath = Path;
CurrentPageStartTime = FPlatformTime::Seconds();
// Set window title to current tutorial name
if (ParentWindowPtr.IsValid())
{
ParentWindowPtr.Pin()->SetTitle(GetCurrentTutorialName());
}
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("PathToDocumentation"), FText::FromString(Path));
const FText Message = FText::Format(LOCTEXT("PageOpenFailMessage", "Unable to access tutorial page \"{PathToDocumentation}\"."), Args);
FNotificationInfo Info(Message);
Info.ExpireDuration = 3.0f;
Info.bUseLargeFont = false;
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if (Notification.IsValid())
{
Notification->SetCompletionState(SNotificationItem::CS_Fail);
}
}
}
}
void SIntroTutorials::SetContentArea()
{
HomeContentArea->ClearContent();
ContentArea->ClearContent();
if (Excerpts.IsValidIndex(CurrentExcerptIndex))
{
auto& Excerpt = Excerpts[CurrentExcerptIndex];
if ( !Excerpt.Content.IsValid() )
{
CurrentPage->GetExcerptContent( Excerpt );
}
TSharedPtr<SBorder> ContentAreaToUse;
if(IsHomeStyle())
{
ContentAreaToUse = HomeContentArea;
}
else
{
ContentAreaToUse = ContentArea;
}
ContentAreaToUse->SetContent(
SNew(SScrollBox)
+SScrollBox::Slot()
[
Excerpt.Content.ToSharedRef()
]);
}
if (ParentWindowPtr.IsValid())
{
ParentWindowPtr.Pin()->BringToFront();
}
}
void SIntroTutorials::PlayDialogue(UDialogueWave* InDialogueWave)
{
if(InDialogueWave == NULL)
{
return;
}
// Set up the tutorial dialogue context
if(TutorialDialogueContext.Speaker == NULL)
{
FString ZakVoicePath(TEXT("/Engine/Tutorial/Audio/Zak.Zak"));
TutorialDialogueContext.Speaker = LoadObject<UDialogueVoice>(NULL, *ZakVoicePath);
if(TutorialDialogueContext.Speaker != NULL)
{
TutorialDialogueContext.Speaker->AddToRoot();
}
TutorialDialogueContext.Targets.Reset();
FString AudienceVoicePath(TEXT("/Engine/Tutorial/Audio/Audience.Audience"));
UDialogueVoice* AudienceVoice = LoadObject<UDialogueVoice>(NULL, *AudienceVoicePath);
if(AudienceVoice != NULL)
{
AudienceVoice->AddToRoot();
TutorialDialogueContext.Targets.Add(AudienceVoice);
}
}
USoundBase* Sound = InDialogueWave->GetWaveFromContext(TutorialDialogueContext);
if(Sound != NULL)
{
// Create audio component
if(DialogueAudioComponent == NULL)
{
DialogueAudioComponent = FAudioDevice::CreateComponent( Sound, NULL, NULL, false );
DialogueAudioComponent->AddToRoot();
}
else
{
DialogueAudioComponent->Stop();
}
DialogueAudioComponent->Sound = Sound;
DialogueAudioComponent->bAutoDestroy = false;
DialogueAudioComponent->bIsUISound = true;
DialogueAudioComponent->bAllowSpatialization = false;
DialogueAudioComponent->bReverb = false;
DialogueAudioComponent->Play();
}
}
void SIntroTutorials::GotoPreviousPage()
{
SetCurrentExcerpt(CurrentExcerptIndex - 1);
}
void SIntroTutorials::GotoNextPage()
{
SetCurrentExcerpt(CurrentExcerptIndex + 1);
}
void SIntroTutorials::SetCurrentExcerpt(int32 NewExcerptIdx)
{
if ( Excerpts.IsValidIndex(NewExcerptIdx) )
{
CurrentExcerptIndex = NewExcerptIdx;
// see if we should skip forward from here
while ( !IsLastPage() && InteractiveTutorials->ShouldSkipExcerpt(Excerpts[CurrentExcerptIndex].Name) )
{
CurrentExcerptIndex++;
}
SetContentArea();
auto& Excerpt = Excerpts[CurrentExcerptIndex];
UDialogueWave* const DialogueWave = InteractiveTutorials->GetDialogueForExcerpt(Excerpt.Name);
if(DialogueWave != NULL)
{
PlayDialogue(DialogueWave);
}
InteractiveTutorials->SetCurrentExcerpt(Excerpt.Name);
bCurrentSectionIsInteractive = InteractiveTutorials->IsExcerptInteractive(Excerpt.Name);
}
}
void SIntroTutorials::TriggerCompleted()
{
InteractiveTutorials->OnExcerptCompleted( Excerpts[CurrentExcerptIndex].Name );
GotoNextPage();
}
EVisibility SIntroTutorials::GetBackButtonVisibility() const
{
// hide back button on first page
return IsFirstPage() ? EVisibility::Hidden : EVisibility::Visible;
}
FText SIntroTutorials::GetBackButtonText() const
{
// "home" on first page, "back" otherwise
return LOCTEXT("BackLabel", "BACK");
}
EVisibility SIntroTutorials::GetNextButtonVisibility() const
{
// hide next button on last page, unless we have a chain
if(IsLastPage())
{
FString NextTutorial;
if(OnGotoNextTutorial.IsBound())
{
NextTutorial = OnGotoNextTutorial.Execute(CurrentPagePath);
}
return NextTutorial.Len() > 0 ? EVisibility::Visible : EVisibility::Hidden;
}
else
{
return EVisibility::Visible;
}
}
FText SIntroTutorials::GetNextButtonText() const
{
// "home" on first page, "back" otherwise
return LOCTEXT("NextLabel", "NEXT");
}
EVisibility SIntroTutorials::GetNavigationVisibility() const
{
return (IsHomeStyle() && (Excerpts.Num() == 1)) ? EVisibility::Collapsed : EVisibility::Visible;
}
FText SIntroTutorials::GetCurrentTutorialName() const
{
return CurrentPage->GetTitle();
}
FText SIntroTutorials::GetTimeRemaining() const
{
if (Excerpts.Num() > 0)
{
FString VariableName = FString::Printf(TEXT("TimeRemaining%d"), CurrentExcerptIndex + 1);
const FString* VariableValue = Excerpts[CurrentExcerptIndex].Variables.Find(VariableName);
if(VariableValue != NULL)
{
return FText::FromString(*VariableValue);
}
}
return FText::GetEmpty();
}
EVisibility SIntroTutorials::GetContentVisibility() const
{
return !IsHomeStyle() ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility SIntroTutorials::GetHomeContentVisibility() const
{
return IsHomeStyle() ? EVisibility::Visible : EVisibility::Collapsed;
}
FReply SIntroTutorials::OnHomeClicked()
{
OnGoHome.ExecuteIfBound();
ChangePage(HomePath);
return FReply::Handled();
}
FReply SIntroTutorials::OnPreviousClicked()
{
if (IsFirstPage())
{
return OnHomeClicked();
}
GotoPreviousPage();
return FReply::Handled();
}
bool SIntroTutorials::OnPreviousIsEnabled() const
{
return Excerpts.IsValidIndex(CurrentExcerptIndex) && InteractiveTutorials->CanManuallyReverseExcerpt( Excerpts[CurrentExcerptIndex].Name );
}
FReply SIntroTutorials::OnNextClicked()
{
if (IsLastPage())
{
if(OnGotoNextTutorial.IsBound())
{
FString NextPage = OnGotoNextTutorial.Execute(CurrentPagePath);
if(NextPage.Len() > 0)
{
// Note: this currently limits us to chaining only two tutorials together
ChangePage(NextPage);
return FReply::Handled();
}
}
return OnHomeClicked();
}
GotoNextPage();
return FReply::Handled();
}
bool SIntroTutorials::OnNextIsEnabled() const
{
return Excerpts.IsValidIndex(CurrentExcerptIndex) && InteractiveTutorials->CanManuallyAdvanceExcerpt(Excerpts[CurrentExcerptIndex].Name);
}
FString SIntroTutorials::GetCurrentExcerptIdentifierName() const
{
if (Excerpts.IsValidIndex(CurrentExcerptIndex))
{
return Excerpts[CurrentExcerptIndex].Name;
}
return FString();
}
FText SIntroTutorials::GetCurrentExcerptTitle() const
{
if (Excerpts.IsValidIndex(CurrentExcerptIndex))
{
// First try for unadorned 'StageTitle'
FString VariableName = FString::Printf(TEXT("StageTitle"));
const FString* VariableValue = Excerpts[CurrentExcerptIndex].Variables.Find(VariableName);
if(VariableValue != NULL)
{
return FText::FromString(*VariableValue);
}
// Then try 'StageTitle<StageNum>'
VariableName = FString::Printf(TEXT("StageTitle%d"), CurrentExcerptIndex + 1);
VariableValue = Excerpts[CurrentExcerptIndex].Variables.Find(VariableName);
if(VariableValue != NULL)
{
return FText::FromString(*VariableValue);
}
}
FFormatNamedArguments Args;
Args.Add(TEXT("CurrentPageNumber"), CurrentExcerptIndex + 1);
return FText::Format(LOCTEXT("GenericStageTitle", "Step {CurrentPageNumber}"), Args);
}
FText SIntroTutorials::GetProgressText() const
{
FFormatNamedArguments Args;
Args.Add(TEXT("CurrentPageNumber"), CurrentExcerptIndex + 1);
Args.Add(TEXT("TotalPages"), Excerpts.Num());
return FText::Format(LOCTEXT("TutorialProgress", "{CurrentPageNumber}/{TotalPages}"), Args);
}
TOptional<float> SIntroTutorials::GetProgress() const
{
if (Excerpts.Num() > 0)
{
return (float)(CurrentExcerptIndex) / (float)(Excerpts.Num() - 1);
}
return 0.0f;
}
const FSlateBrush* SIntroTutorials::GetContentAreaBackground() const
{
return FEditorStyle::GetBrush("Tutorials.ContentAreaBackground");
}
int32 SIntroTutorials::GetCurrentExcerptIndex() const
{
return CurrentExcerptIndex;
}
FString SIntroTutorials::GetCurrentPagePath() const
{
return CurrentPagePath;
}
float SIntroTutorials::GetCurrentPageElapsedTime() const
{
return (float)(FPlatformTime::Seconds() - CurrentPageStartTime);
}
void SIntroTutorials::SetOnGoHome(const FOnGoHome& InDelegate)
{
OnGoHome = InDelegate;
}
bool SIntroTutorials::IsLastPage() const
{
return (CurrentExcerptIndex == (Excerpts.Num() - 1));
}
bool SIntroTutorials::IsFirstPage() const
{
return CurrentExcerptIndex == 0;
}
#undef LOCTEXT_NAMESPACE