2014-12-07 19:09:38 -05:00
|
|
|
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
2014-08-28 06:22:40 -04:00
|
|
|
|
|
|
|
|
#include "IntroTutorialsPrivatePCH.h"
|
|
|
|
|
#include "STutorialButton.h"
|
|
|
|
|
#include "EditorTutorialSettings.h"
|
|
|
|
|
#include "TutorialStateSettings.h"
|
2014-09-05 07:39:52 -04:00
|
|
|
#include "TutorialMetaData.h"
|
2014-09-22 09:42:41 -04:00
|
|
|
#include "EngineBuildSettings.h"
|
2014-08-28 06:22:40 -04:00
|
|
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "STutorialButton"
|
|
|
|
|
|
|
|
|
|
namespace TutorialButtonConstants
|
|
|
|
|
{
|
|
|
|
|
const float MaxPulseOffset = 32.0f;
|
|
|
|
|
const float PulseAnimationLength = 2.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void STutorialButton::Construct(const FArguments& InArgs)
|
|
|
|
|
{
|
|
|
|
|
Context = InArgs._Context;
|
|
|
|
|
ContextWindow = InArgs._ContextWindow;
|
|
|
|
|
|
2014-09-22 09:42:41 -04:00
|
|
|
bTestAlerts = FParse::Param(FCommandLine::Get(), TEXT("TestTutorialAlerts"));
|
|
|
|
|
|
2014-08-28 06:22:40 -04:00
|
|
|
bTutorialAvailable = false;
|
|
|
|
|
bTutorialCompleted = false;
|
|
|
|
|
bTutorialDismissed = false;
|
|
|
|
|
bDeferTutorialOpen = true;
|
2014-09-18 08:10:29 -04:00
|
|
|
AlertStartTime = 0.0f;
|
2014-08-28 06:22:40 -04:00
|
|
|
|
|
|
|
|
PulseAnimation.AddCurve(0.0f, TutorialButtonConstants::PulseAnimationLength, ECurveEaseFunction::Linear);
|
|
|
|
|
PulseAnimation.Play();
|
|
|
|
|
|
|
|
|
|
ChildSlot
|
|
|
|
|
[
|
|
|
|
|
SNew(SButton)
|
2014-09-12 06:18:47 -04:00
|
|
|
.AddMetaData<FTagMetaData>(*FString::Printf(TEXT("%s.TutorialLaunchButton"), *Context.ToString()))
|
2014-08-28 06:22:40 -04:00
|
|
|
.ButtonStyle(FEditorStyle::Get(), "TutorialLaunch.Button")
|
2014-09-10 08:09:10 -04:00
|
|
|
.ToolTipText(this, &STutorialButton::GetButtonToolTip)
|
2014-08-28 06:22:40 -04:00
|
|
|
.OnClicked(this, &STutorialButton::HandleButtonClicked)
|
|
|
|
|
.ContentPadding(0.0f)
|
|
|
|
|
[
|
|
|
|
|
SNew(SBox)
|
|
|
|
|
.WidthOverride(16)
|
|
|
|
|
.HeightOverride(16)
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void STutorialButton::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
|
|
|
|
{
|
|
|
|
|
if (bDeferTutorialOpen)
|
|
|
|
|
{
|
2014-09-18 10:28:16 -04:00
|
|
|
RefreshStatus();
|
2014-08-28 06:22:40 -04:00
|
|
|
|
2014-09-18 10:28:16 -04:00
|
|
|
if (bTutorialAvailable && CachedAttractTutorial != nullptr && !bTutorialDismissed && !bTutorialCompleted)
|
2014-08-28 06:22:40 -04:00
|
|
|
{
|
|
|
|
|
// kick off the attract tutorial if the user hasn't dismissed it and hasn't completed it
|
|
|
|
|
FIntroTutorials& IntroTutorials = FModuleManager::GetModuleChecked<FIntroTutorials>(TEXT("IntroTutorials"));
|
|
|
|
|
const bool bRestart = true;
|
2014-09-18 10:28:16 -04:00
|
|
|
IntroTutorials.LaunchTutorial(CachedAttractTutorial, bRestart, ContextWindow);
|
2014-08-28 06:22:40 -04:00
|
|
|
}
|
2014-09-18 08:10:29 -04:00
|
|
|
|
|
|
|
|
if(ShouldShowAlert())
|
|
|
|
|
{
|
|
|
|
|
AlertStartTime = FPlatformTime::Seconds();
|
|
|
|
|
}
|
2014-09-18 08:32:42 -04:00
|
|
|
|
2014-09-18 10:28:16 -04:00
|
|
|
if(CachedLaunchTutorial != nullptr)
|
2014-09-18 08:32:42 -04:00
|
|
|
{
|
2014-09-18 10:28:16 -04:00
|
|
|
TutorialTitle = CachedLaunchTutorial->Title;
|
2014-09-18 08:32:42 -04:00
|
|
|
}
|
2014-08-28 06:22:40 -04:00
|
|
|
}
|
|
|
|
|
bDeferTutorialOpen = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void GetAnimationValues(float InAnimationProgress, float& OutAlphaFactor0, float& OutPulseFactor0, float& OutAlphaFactor1, float& OutPulseFactor1)
|
|
|
|
|
{
|
|
|
|
|
InAnimationProgress = FMath::Fmod(InAnimationProgress * 2.0f, 1.0f);
|
|
|
|
|
|
|
|
|
|
OutAlphaFactor0 = FMath::Square(1.0f - InAnimationProgress);
|
|
|
|
|
OutPulseFactor0 = 1.0f - FMath::Square(1.0f - InAnimationProgress);
|
|
|
|
|
|
|
|
|
|
float OffsetProgress = FMath::Fmod(InAnimationProgress + 0.25f, 1.0f);
|
|
|
|
|
OutAlphaFactor1 = FMath::Square(1.0f - OffsetProgress);
|
|
|
|
|
OutPulseFactor1 = 1.0f - FMath::Square(1.0f - OffsetProgress);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 STutorialButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
|
|
|
|
{
|
|
|
|
|
LayerId = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled) + 1000;
|
|
|
|
|
|
2014-09-10 08:09:10 -04:00
|
|
|
if (ShouldShowAlert())
|
2014-08-28 06:22:40 -04:00
|
|
|
{
|
|
|
|
|
float AlphaFactor0 = 0.0f;
|
|
|
|
|
float AlphaFactor1 = 0.0f;
|
|
|
|
|
float PulseFactor0 = 0.0f;
|
|
|
|
|
float PulseFactor1 = 0.0f;
|
|
|
|
|
GetAnimationValues(PulseAnimation.GetLerpLooping(), AlphaFactor0, PulseFactor0, AlphaFactor1, PulseFactor1);
|
|
|
|
|
|
|
|
|
|
const FSlateBrush* PulseBrush = FEditorStyle::Get().GetBrush(TEXT("TutorialLaunch.Circle"));
|
|
|
|
|
const FLinearColor PulseColor = FEditorStyle::Get().GetColor(TEXT("TutorialLaunch.Circle.Color"));
|
|
|
|
|
|
|
|
|
|
// We should be clipped by the window size, not our containing widget, as we want to draw outside the widget
|
|
|
|
|
const FVector2D WindowSize = OutDrawElements.GetWindow()->GetSizeInScreen();
|
|
|
|
|
FSlateRect WindowClippingRect(0.0f, 0.0f, WindowSize.X, WindowSize.Y);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FVector2D PulseOffset = FVector2D(PulseFactor0 * TutorialButtonConstants::MaxPulseOffset, PulseFactor0 * TutorialButtonConstants::MaxPulseOffset);
|
|
|
|
|
|
|
|
|
|
FVector2D BorderPosition = (AllottedGeometry.AbsolutePosition - ((FVector2D(PulseBrush->Margin.Left, PulseBrush->Margin.Top) * PulseBrush->ImageSize * AllottedGeometry.Scale) + PulseOffset));
|
|
|
|
|
FVector2D BorderSize = ((AllottedGeometry.Size * AllottedGeometry.Scale) + (PulseOffset * 2.0f) + (FVector2D(PulseBrush->Margin.Right * 2.0f, PulseBrush->Margin.Bottom * 2.0f) * PulseBrush->ImageSize * AllottedGeometry.Scale));
|
|
|
|
|
|
|
|
|
|
FPaintGeometry BorderGeometry(BorderPosition, BorderSize, AllottedGeometry.Scale);
|
|
|
|
|
|
|
|
|
|
// draw highlight border
|
|
|
|
|
FSlateDrawElement::MakeBox(OutDrawElements, LayerId++, BorderGeometry, PulseBrush, WindowClippingRect, ESlateDrawEffect::None, FLinearColor(PulseColor.R, PulseColor.G, PulseColor.B, AlphaFactor0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FVector2D PulseOffset = FVector2D(PulseFactor1 * TutorialButtonConstants::MaxPulseOffset, PulseFactor1 * TutorialButtonConstants::MaxPulseOffset);
|
|
|
|
|
|
|
|
|
|
FVector2D BorderPosition = (AllottedGeometry.AbsolutePosition - ((FVector2D(PulseBrush->Margin.Left, PulseBrush->Margin.Top) * PulseBrush->ImageSize * AllottedGeometry.Scale) + PulseOffset));
|
|
|
|
|
FVector2D BorderSize = ((AllottedGeometry.Size * AllottedGeometry.Scale) + (PulseOffset * 2.0f) + (FVector2D(PulseBrush->Margin.Right * 2.0f, PulseBrush->Margin.Bottom * 2.0f) * PulseBrush->ImageSize * AllottedGeometry.Scale));
|
|
|
|
|
|
|
|
|
|
FPaintGeometry BorderGeometry(BorderPosition, BorderSize, AllottedGeometry.Scale);
|
|
|
|
|
|
|
|
|
|
// draw highlight border
|
|
|
|
|
FSlateDrawElement::MakeBox(OutDrawElements, LayerId++, BorderGeometry, PulseBrush, WindowClippingRect, ESlateDrawEffect::None, FLinearColor(PulseColor.R, PulseColor.G, PulseColor.B, AlphaFactor1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LayerId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FReply STutorialButton::HandleButtonClicked()
|
|
|
|
|
{
|
2014-09-19 07:03:06 -04:00
|
|
|
RefreshStatus();
|
2014-09-10 08:09:10 -04:00
|
|
|
|
2014-09-19 07:03:06 -04:00
|
|
|
if( FEngineAnalytics::IsAvailable() )
|
2014-08-28 06:22:40 -04:00
|
|
|
{
|
2014-09-18 16:42:40 -04:00
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes;
|
|
|
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Context"), Context.ToString()));
|
|
|
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("TimeSinceAlertStarted"), (AlertStartTime != 0.0f && ShouldShowAlert()) ? (FPlatformTime::Seconds() - AlertStartTime) : -1.0f));
|
|
|
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("LaunchedBrowser"), ShouldLaunchBrowser()));
|
2014-09-18 08:10:29 -04:00
|
|
|
|
2014-09-19 07:03:06 -04:00
|
|
|
FEngineAnalytics::GetProvider().RecordEvent( TEXT("Rocket.Tutorials.ClickedContextButton"), EventAttributes );
|
2014-09-18 16:42:40 -04:00
|
|
|
}
|
2014-09-18 08:10:29 -04:00
|
|
|
|
2014-09-18 16:42:40 -04:00
|
|
|
FIntroTutorials& IntroTutorials = FModuleManager::GetModuleChecked<FIntroTutorials>(TEXT("IntroTutorials"));
|
2014-09-19 07:03:06 -04:00
|
|
|
if(ShouldLaunchBrowser())
|
2014-09-18 16:42:40 -04:00
|
|
|
{
|
2014-10-13 06:46:06 -04:00
|
|
|
IntroTutorials.SummonTutorialBrowser();
|
2014-09-18 16:42:40 -04:00
|
|
|
}
|
2014-09-19 07:03:06 -04:00
|
|
|
else if (CachedLaunchTutorial != nullptr)
|
2014-09-18 16:42:40 -04:00
|
|
|
{
|
2014-09-19 07:03:06 -04:00
|
|
|
auto Delegate = FSimpleDelegate::CreateSP(this, &STutorialButton::HandleTutorialExited);
|
|
|
|
|
|
2014-09-18 16:42:40 -04:00
|
|
|
const bool bRestart = true;
|
2014-09-19 07:03:06 -04:00
|
|
|
IntroTutorials.LaunchTutorial(CachedLaunchTutorial, bRestart, ContextWindow, Delegate, Delegate);
|
|
|
|
|
|
|
|
|
|
const bool bDismissAcrossSessions = true;
|
|
|
|
|
GetMutableDefault<UTutorialStateSettings>()->DismissTutorial(CachedLaunchTutorial, bDismissAcrossSessions);
|
|
|
|
|
GetMutableDefault<UTutorialStateSettings>()->SaveProgress();
|
|
|
|
|
bTutorialDismissed = true;
|
2014-08-28 06:22:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FReply::Handled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FReply STutorialButton::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
|
|
|
{
|
2014-09-18 08:32:42 -04:00
|
|
|
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
2014-08-28 06:22:40 -04:00
|
|
|
{
|
2014-09-18 08:32:42 -04:00
|
|
|
const bool bInShouldCloseWindowAfterMenuSelection = true;
|
|
|
|
|
FMenuBuilder MenuBuilder(bInShouldCloseWindowAfterMenuSelection, nullptr);
|
|
|
|
|
|
|
|
|
|
if(ShouldShowAlert())
|
2014-09-10 08:09:10 -04:00
|
|
|
{
|
|
|
|
|
MenuBuilder.AddMenuEntry(
|
2014-09-18 08:32:42 -04:00
|
|
|
LOCTEXT("DismissReminder", "Dismiss Alert"),
|
|
|
|
|
LOCTEXT("DismissReminderTooltip", "Don't show me this alert again"),
|
2014-09-10 08:09:10 -04:00
|
|
|
FSlateIcon(),
|
2014-09-18 08:32:42 -04:00
|
|
|
FUIAction(FExecuteAction::CreateSP(this, &STutorialButton::DismissAlert))
|
2014-09-10 08:09:10 -04:00
|
|
|
);
|
|
|
|
|
}
|
2014-09-18 08:32:42 -04:00
|
|
|
|
|
|
|
|
if(bTutorialAvailable)
|
|
|
|
|
{
|
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
|
|
|
FText::Format(LOCTEXT("LaunchTutorialPattern", "Open Tutorial: {0}"), TutorialTitle),
|
|
|
|
|
LOCTEXT("LaunchTutorialTooltip", "Launch this tutorial"),
|
|
|
|
|
FSlateIcon(),
|
|
|
|
|
FUIAction(FExecuteAction::CreateSP(this, &STutorialButton::LaunchTutorial))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
|
|
|
LOCTEXT("LaunchBrowser", "Show Available Tutorials"),
|
|
|
|
|
LOCTEXT("LaunchBrowserTooltip", "Display the tutorials browser"),
|
|
|
|
|
FSlateIcon(),
|
|
|
|
|
FUIAction(FExecuteAction::CreateSP(this, &STutorialButton::LaunchBrowser))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FSlateApplication::Get().PushMenu(SharedThis(this), MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect::ContextMenu);
|
2014-08-28 06:22:40 -04:00
|
|
|
}
|
|
|
|
|
return FReply::Handled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void STutorialButton::DismissAlert()
|
|
|
|
|
{
|
2014-09-18 10:28:16 -04:00
|
|
|
RefreshStatus();
|
|
|
|
|
|
2014-09-19 07:03:06 -04:00
|
|
|
if( FEngineAnalytics::IsAvailable() )
|
|
|
|
|
{
|
|
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes;
|
|
|
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Context"), Context.ToString()));
|
|
|
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("TimeSinceAlertStarted"), (AlertStartTime != 0.0f && ShouldShowAlert()) ? (FPlatformTime::Seconds() - AlertStartTime) : -1.0f));
|
2014-09-18 08:10:29 -04:00
|
|
|
|
2014-09-19 07:03:06 -04:00
|
|
|
FEngineAnalytics::GetProvider().RecordEvent( TEXT("Rocket.Tutorials.DismissedTutorialAlert"), EventAttributes );
|
|
|
|
|
}
|
2014-09-18 10:28:16 -04:00
|
|
|
|
2014-09-19 07:03:06 -04:00
|
|
|
const bool bDismissAcrossSessions = true;
|
2014-09-18 10:28:16 -04:00
|
|
|
if (CachedAttractTutorial != nullptr)
|
|
|
|
|
{
|
|
|
|
|
GetMutableDefault<UTutorialStateSettings>()->DismissTutorial(CachedAttractTutorial, bDismissAcrossSessions);
|
|
|
|
|
}
|
|
|
|
|
if( CachedLaunchTutorial != nullptr)
|
|
|
|
|
{
|
|
|
|
|
GetMutableDefault<UTutorialStateSettings>()->DismissTutorial(CachedLaunchTutorial, bDismissAcrossSessions);
|
|
|
|
|
}
|
2014-09-19 07:03:06 -04:00
|
|
|
GetMutableDefault<UTutorialStateSettings>()->SaveProgress();
|
|
|
|
|
bTutorialDismissed = true;
|
2014-09-18 10:28:16 -04:00
|
|
|
|
2014-09-19 07:03:06 -04:00
|
|
|
FIntroTutorials& IntroTutorials = FModuleManager::GetModuleChecked<FIntroTutorials>(TEXT("IntroTutorials"));
|
|
|
|
|
IntroTutorials.CloseAllTutorialContent();
|
|
|
|
|
}
|
2014-08-28 06:22:40 -04:00
|
|
|
|
2014-09-18 08:32:42 -04:00
|
|
|
void STutorialButton::LaunchTutorial()
|
|
|
|
|
{
|
|
|
|
|
HandleButtonClicked();
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-10 08:09:10 -04:00
|
|
|
void STutorialButton::LaunchBrowser()
|
|
|
|
|
{
|
2014-09-22 09:42:52 -04:00
|
|
|
RefreshStatus();
|
2014-09-10 08:09:10 -04:00
|
|
|
|
2014-09-22 09:42:52 -04:00
|
|
|
FIntroTutorials& IntroTutorials = FModuleManager::GetModuleChecked<FIntroTutorials>(TEXT("IntroTutorials"));
|
2014-10-13 06:46:06 -04:00
|
|
|
IntroTutorials.SummonTutorialBrowser();
|
2014-09-10 08:09:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool STutorialButton::ShouldLaunchBrowser() const
|
|
|
|
|
{
|
|
|
|
|
return (!bTutorialAvailable || (bTutorialAvailable && bTutorialCompleted));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool STutorialButton::ShouldShowAlert() const
|
|
|
|
|
{
|
2014-09-22 09:42:41 -04:00
|
|
|
return ((bTestAlerts || !FEngineBuildSettings::IsInternalBuild()) && bTutorialAvailable && !(bTutorialCompleted || bTutorialDismissed));
|
2014-09-10 08:09:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FText STutorialButton::GetButtonToolTip() const
|
|
|
|
|
{
|
|
|
|
|
if(ShouldLaunchBrowser())
|
|
|
|
|
{
|
|
|
|
|
return LOCTEXT("TutorialLaunchBrowserToolTip", "Show Available Tutorials");
|
|
|
|
|
}
|
2014-09-18 08:32:42 -04:00
|
|
|
else if(bTutorialAvailable)
|
|
|
|
|
{
|
|
|
|
|
return FText::Format(LOCTEXT("TutorialLaunchToolTipPattern", "Open: {0}\nRight-Click for More Options"), TutorialTitle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LOCTEXT("TutorialToolTip", "Take Tutorial");
|
2014-09-10 08:09:10 -04:00
|
|
|
}
|
|
|
|
|
|
2014-09-18 10:28:16 -04:00
|
|
|
void STutorialButton::RefreshStatus()
|
|
|
|
|
{
|
|
|
|
|
CachedAttractTutorial = nullptr;
|
|
|
|
|
CachedLaunchTutorial = nullptr;
|
|
|
|
|
CachedBrowserFilter = TEXT("");
|
|
|
|
|
GetDefault<UEditorTutorialSettings>()->FindTutorialInfoForContext(Context, CachedAttractTutorial, CachedLaunchTutorial, CachedBrowserFilter);
|
|
|
|
|
|
|
|
|
|
bTutorialAvailable = (CachedLaunchTutorial != nullptr);
|
|
|
|
|
bTutorialCompleted = (CachedLaunchTutorial != nullptr) && GetDefault<UTutorialStateSettings>()->HaveCompletedTutorial(CachedLaunchTutorial);
|
|
|
|
|
bTutorialDismissed = ((CachedAttractTutorial != nullptr) && GetDefault<UTutorialStateSettings>()->IsTutorialDismissed(CachedAttractTutorial)) ||
|
|
|
|
|
((CachedLaunchTutorial != nullptr) && GetDefault<UTutorialStateSettings>()->IsTutorialDismissed(CachedLaunchTutorial));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void STutorialButton::HandleTutorialExited()
|
|
|
|
|
{
|
|
|
|
|
RefreshStatus();
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-28 06:22:40 -04:00
|
|
|
#undef LOCTEXT_NAMESPACE
|