Files
UnrealEngineUWP/Engine/Source/Editor/UserFeedback/Private/UserFeedback.cpp

654 lines
19 KiB
C++
Raw Normal View History

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "IUserFeedbackModule.h"
#include "SlateBasics.h"
#include "SlateStyle.h"
#include "ModuleManager.h"
#include "IAnalyticsProvider.h"
#include "EngineAnalytics.h"
#include "UnrealEd.h"
#include "SNotificationList.h"
#include "NotificationManager.h"
#define LOCTEXT_NAMESPACE "UserFeedback"
/** Feedback mode - positive or negative */
namespace EFeedbackMode
{
enum Type { Positive, Negative };
}
/** The feedback widget itself which contains the UI for submitting feedback */
class SUserFeedbackWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SUserFeedbackWidget)
: _Context(), _Mode(EFeedbackMode::Positive){}
SLATE_ARGUMENT(FText, Context)
SLATE_ARGUMENT(EFeedbackMode::Type, Mode)
SLATE_EVENT(FSimpleDelegate, OnFeedbackSent)
SLATE_EVENT(FSimpleDelegate, OnCloseClicked)
SLATE_END_ARGS()
/** Constructor */
SUserFeedbackWidget()
: bUserSentFeedback(false)
, Sequence(0, 0.2f, ECurveEaseFunction::CubicIn)
{
}
/** Destructor - records an analytics event if the user did not send feedback */
~SUserFeedbackWidget()
{
if (!bUserSentFeedback && FEngineAnalytics::IsAvailable())
{
FEngineAnalytics::GetProvider().RecordEvent(FString("Editor.Feedback.Canceled"));
}
}
/** Construct this widget
*
* @param InArgs The declaration data for this widget
*/
void Construct(const FArguments& InArgs)
{
// Animate ourselves in if we're running at the target frame rate
if (FSlateApplication::Get().IsRunningAtTargetFrameRate())
{
---- Merging with SlateDev branch ---- Introduces the concept of "Active Ticking" to allow Slate to go to sleep when there is no need to update the UI. While asleep, Slate will skip the Tick & Paint pass for that frame entirely. - There are TWO ways to "wake" Slate and cause a Tick/Paint pass: 1. Provide some sort of input (mouse movement, clicks, and key presses). Slate will always tick when the user is active. - Therefore, if the logic in a given widget's Tick is only relevant in response to user action, there is no need to register an active tick. 2. Register an Active Tick. Currently this is an all-or-nothing situation, so if a single active tick needs to execute, all of Slate will be ticked. - The purpose of an Active Tick is to allow a widget to "drive" Slate and guarantee a Tick/Paint pass in the absence of any user action. - Examples include animation, async operations that update periodically, progress updates, loading bars, etc. - An empty active tick is registered for viewports when they are real-time, so game project widgets are unaffected by this change and should continue to work as before. - An Active Tick is registered by creating an FWidgetActiveTickDelegate and passing it to SWidget::RegisterActiveTick() - There are THREE ways to unregister an active tick: 1. Return EActiveTickReturnType::StopTicking from the active tick function 2. Pass the FActiveTickHandle returned by RegisterActiveTick() to SWidget::UnregisterActiveTick() 3. Destroy the widget responsible for the active tick - Sleeping is currently disabled, can be enabled with Slate.AllowSlateToSleep cvar - There is currently a little buffer time during which Slate continues to tick following any input. Long-term, this is planned to be removed. - The duration of the buffer can be adjusted using Slate.SleepBufferPostInput cvar (defaults to 1.0f) - The FCurveSequence API has been updated to work with the active tick system - Playing a curve sequence now requires that you pass the widget being animated by the sequence - The active tick will automatically be registered on behalf of the widget and unregister when the sequence is complete - GetLerpLooping() has been removed. Instead, pass true as the second param to Play() to indicate that the animation will loop. This causes the active tick to be registered indefinitely until paused or jumped to the start/end. [CL 2391669 by Dan Hertzka in Main branch]
2014-12-17 16:07:57 -05:00
Sequence.Play( this->AsShared() );
}
else
{
Sequence.JumpToEnd();
}
OnFeedbackSent = InArgs._OnFeedbackSent;
OnCloseClicked = InArgs._OnCloseClicked;
Mode = InArgs._Mode;
PopulateContextNames(InArgs._Context);
const FMargin DefaultPadding(5, 5, 5, 5);
ChildSlot
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SBox)
.Padding(FMargin(8,5,8,5))
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.Padding(DefaultPadding)
.AutoHeight()
[
SNew(STextBlock)
.Text(this, &SUserFeedbackWidget::GetDescriptionText)
]
+SVerticalBox::Slot()
.Padding(DefaultPadding)
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
[
SNew(SComboButton)
.ContentPadding(FMargin(5,2,5,2))
.OnGetMenuContent(this, &SUserFeedbackWidget::GetComboBoxMenuContent)
.ButtonContent()
[
SNew(STextBlock)
.Text(this, &SUserFeedbackWidget::GetComboBoxText)
]
]
+SHorizontalBox::Slot()
.FillWidth(1)
.Padding(FMargin(5,0,0,0))
[
SAssignNew(TextBox, SEditableTextBox)
.Padding(2)
.OnTextChanged(this, &SUserFeedbackWidget::OnTextChanged)
.HintText(LOCTEXT("FeedbackTextBox_HintText", "Tell us more"))
.ErrorReporting
(
SNew( SPopupErrorText )
.ShowInNewWindow( true )
)
]
]
+SVerticalBox::Slot()
.Padding(DefaultPadding)
.AutoHeight()
.HAlign(HAlign_Right)
[
SNew(SUniformGridPanel)
.SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding"))
.MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
.MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight"))
+SUniformGridPanel::Slot(0,0)
[
SNew(SButton)
.HAlign(HAlign_Center)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.OnClicked(this, &SUserFeedbackWidget::OnFeedbackSubmitted)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SImage).Image(FEditorStyle::GetBrush(Mode == EFeedbackMode::Positive ? "UserFeedback.PositiveIcon" : "UserFeedback.NegativeIcon"))
]
+SHorizontalBox::Slot()
.Padding(FMargin(0,0,3,0))
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(STextBlock).Text(LOCTEXT("SubmitFeedback", "Send"))
]
]
]
+SUniformGridPanel::Slot(1,0)
[
SNew(SButton)
.HAlign(HAlign_Center)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.OnClicked(this, &SUserFeedbackWidget::OnCloseButtonClicked)
.Text(LOCTEXT("CancelButton", "Cancel"))
]
]
]
]
]
];
TextBox->SetError( FText::GetEmpty() );
}
/** Called when a key is pressed on the widget */
virtual FReply OnPreviewKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override
{
if (InKeyEvent.GetKey() == EKeys::Escape)
{
return OnCloseButtonClicked();
}
else if (InKeyEvent.GetKey() == EKeys::Enter)
{
return OnFeedbackSubmitted();
}
return FReply::Unhandled();
}
/** @return The feedback mode for this widget */
EFeedbackMode::Type GetFeedbackMode() const
{
return Mode;
}
private:
/** Setup the array of context names */
void PopulateContextNames(const FText& SuppliedContext)
{
// ------------------------------------
// Fill the context display names array
ContextDisplayNames.Add(LOCTEXT("LevelEditing", "Level Editing"));
ContextDisplayNames.Add(LOCTEXT("ContentBrowser", "Content Browser"));
ContextDisplayNames.Add(LOCTEXT("PlayInEditor", "Play In Editor"));
ContextDisplayNames.Add(LOCTEXT("AssetCreation", "Asset Creation"));
ContextDisplayNames.Add(LOCTEXT("DetailsPanel", "Details Panel"));
ContextDisplayNames.Add(LOCTEXT("Tutorials", "Tutorials"));
ContextDisplayNames.Add(LOCTEXT("Other", "Other"));
ContextMarkers.FirstEditor = ContextDisplayNames.Num();
ContextDisplayNames.Add(NSLOCTEXT("BlueprintEditor", "AppLabel", "Blueprint Editor"));
ContextDisplayNames.Add(NSLOCTEXT("Matinee", "AppLabel", "Matinee"));
ContextDisplayNames.Add(NSLOCTEXT("StaticMeshEditor", "AppLabel", "StaticMesh Editor"));
ContextDisplayNames.Add(NSLOCTEXT("MaterialEditor", "AppLabel", "Material Editor"));
ContextDisplayNames.Add(NSLOCTEXT("PhAT", "AppLabel", "PhAT"));
ContextDisplayNames.Add(NSLOCTEXT("Cascade", "AppLabel", "Cascade"));
ContextDisplayNames.Add(NSLOCTEXT("FPersona", "AppLabel", "Persona"));
ContextMarkers.Custom = -1;
// Try and match an existing context before adding our own
for (auto Iter = ContextDisplayNames.CreateConstIterator(); Iter; ++Iter)
{
if (SuppliedContext.EqualTo(*Iter))
{
SelectedContextIndex = Iter.GetIndex();
return;
}
}
// Add the supplied context to the list since it doesn't already exist
SelectedContextIndex = ContextMarkers.Custom = ContextDisplayNames.Num();
ContextDisplayNames.Add(SuppliedContext);
}
/** Called when the text on the feedback form is changed by the user */
void OnTextChanged(const FText& InLabel)
{
static const int32 MaxSize = 250;
FString TextString = InLabel.ToString();
if (TextString.Len() >= MaxSize)
{
FFormatNamedArguments Args;
Args.Add(TEXT("MaxSize"), MaxSize);
TextBox->SetError(FText::Format(LOCTEXT("TooLong", "Additional feedback must be fewer than {MaxSize} characters."), Args));
TextBox->SetText(FText::FromString(TextString.Left(MaxSize)));
}
else
{
TextBox->SetError(FText::GetEmpty());
}
}
/** @return the description text for the widget */
FText GetDescriptionText() const
{
static const FText ApplicationTitle = NSLOCTEXT("UnrealEditor", "ApplicationTitle", "Unreal Editor");
FFormatNamedArguments Args;
Args.Add(TEXT("Tool"), ApplicationTitle);
if (Mode == EFeedbackMode::Positive)
{
return FText::Format(LOCTEXT("Description_Positive", "We value your feedback. What part of {Tool} do you like?"), Args);
}
else
{
return FText::Format(LOCTEXT("Description_Negative", "We value your feedback. What could we improve about {Tool}?"), Args);
}
}
/** @return the combo box menu content */
TSharedRef<SWidget> GetComboBoxMenuContent()
{
const bool bShouldCloseWindowAfterMenuSelection = true, bCloseSelfOnly = true;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, MakeShareable(new FUICommandList), TSharedPtr<FExtender>(), bCloseSelfOnly);
int8 MenuIndex = 0;
// --------------------------------
// General editing contexts
MenuBuilder.BeginSection(TEXT("UserFeedbackGeneral"), LOCTEXT("UserFeedbackCombo_General", "General:"));
{
for (; MenuIndex < ContextMarkers.FirstEditor; ++MenuIndex)
{
MenuBuilder.AddMenuEntry(ContextDisplayNames[MenuIndex], FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &SUserFeedbackWidget::SetCurrentContext, MenuIndex)));
}
}
MenuBuilder.EndSection();
// --------------------------------
// Asset editor contexts
MenuBuilder.BeginSection(TEXT("UserFeedbackAssets"), LOCTEXT("UserFeedbackCombo_Assets", "Asset Editors:"));
{
const int8 End = ContextMarkers.Custom == -1 ? ContextDisplayNames.Num() - 1 : ContextMarkers.Custom;
for (; MenuIndex < End; ++MenuIndex)
{
MenuBuilder.AddMenuEntry(ContextDisplayNames[MenuIndex], FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &SUserFeedbackWidget::SetCurrentContext, MenuIndex)));
}
}
MenuBuilder.EndSection();
// -----------------------------------------------------------------
// Add the custom context supplied by the client code, if applicable
if (ContextMarkers.Custom != -1)
{
MenuBuilder.BeginSection(TEXT("UserFeedbackCurrent"), LOCTEXT("UserFeedbackCombo_Current", "Current:"));
{
MenuBuilder.AddMenuEntry(ContextDisplayNames[ContextMarkers.Custom], FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &SUserFeedbackWidget::SetCurrentContext, ContextMarkers.Custom)));
}
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
/** @return the text displayed on the combo box */
FText GetComboBoxText() const
{
return ContextDisplayNames[SelectedContextIndex];
}
/** Set the current context that the user wants to provide feedback for */
void SetCurrentContext(const int8 Context)
{
SelectedContextIndex = Context;
}
/** Called when the user intentionally closes the window */
FReply OnCloseButtonClicked()
{
OnCloseClicked.ExecuteIfBound();
return FReply::Handled();
}
/** Called when the user sends some feedback */
FReply OnFeedbackSubmitted()
{
// Get the current feedback text. Ensure the user actually typed something
FText UserFeedbackText = FText::TrimPrecedingAndTrailing( TextBox->GetText() );
if( !UserFeedbackText.IsEmpty() )
{
if (FEngineAnalytics::IsAvailable())
{
TArray<FAnalyticsEventAttribute> Attributes;
Attributes.Add(FAnalyticsEventAttribute(TEXT("Positive"), Mode == EFeedbackMode::Positive));
Attributes.Add(FAnalyticsEventAttribute(TEXT("Context"), ContextDisplayNames[SelectedContextIndex].ToString()));
Attributes.Add(FAnalyticsEventAttribute(TEXT("Comment"), TextBox->GetText().ToString()));
FEngineAnalytics::GetProvider().RecordEvent(FString("Editor.Feedback.Submitted"), Attributes);
}
FNotificationInfo Info(LOCTEXT("FeedbackSent", "Thank you for sending us your feedback"));
Info.bUseLargeFont = false;
Info.bUseThrobber = false;
Info.bUseSuccessFailIcons = false;
Info.Image = FEditorStyle::GetBrush("NoBrush");
Info.ExpireDuration = 3.f;
FSlateNotificationManager::Get().AddNotification(Info);
bUserSentFeedback = true;
OnFeedbackSent.ExecuteIfBound();
}
else
{
TextBox->SetError( LOCTEXT("FeedbackErrorMessage", "Please provide some feedback") );
}
return FReply::Handled();
}
/** true when the user sent feedback, false when the popup was closed without sending feedback */
bool bUserSentFeedback;
/** Pointer to the editable text box on the widget */
TSharedPtr<SEditableTextBox> TextBox;
/** The current feedback mode (positive/negative) */
EFeedbackMode::Type Mode;
/** Delegates that are called when feedback is sent and when the close button is clicked */
FSimpleDelegate OnFeedbackSent, OnCloseClicked;
/** Animation sequence for loading ourselves */
FCurveSequence Sequence;
/** Display names for the context shown in the combo box */
TArray<FText> ContextDisplayNames;
/** Markers which specify the indices of the start of particular groups in the display name array */
struct
{
int8 FirstEditor; // The index of the first editor context
int8 Custom; // The index of the context supplied by the client code (-1 if invalid)
} ContextMarkers;
/** The currently selected context index */
int8 SelectedContextIndex;
};
/** A menu item in the feedback widget's menu */
class SUserFeedbackMenuItem : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( SUserFeedbackMenuItem )
: _MinWidth(0)
{}
SLATE_DEFAULT_SLOT(FArguments, Content)
SLATE_ATTRIBUTE(float, MinWidth)
SLATE_ATTRIBUTE(FText, Text)
SLATE_ARGUMENT(FName, Icon)
SLATE_ATTRIBUTE(EFeedbackMode::Type, Mode)
SLATE_ATTRIBUTE(const FSlateBrush*, ArrowBrush)
SLATE_END_ARGS()
/** Construct this menu item */
void Construct( const FArguments& InArgs )
{
MinWidth = InArgs._MinWidth;
TSharedRef<SWidget> IconWidget = SNullWidget::NullWidget;
if ( InArgs._Icon != NAME_None )
{
const FSlateBrush* IconBrush = FEditorStyle::GetOptionalBrush(InArgs._Icon);
if (IconBrush->GetResourceName() != NAME_None)
{
IconWidget =
SNew( SImage )
.Image(IconBrush);
}
}
ChildSlot
[
SNew( SHorizontalBox )
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(2, 0, 2, 0))
[
SNew( SBox )
.WidthOverride(20)
.HeightOverride(20.f)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
IconWidget
]
]
+ SHorizontalBox::Slot()
.FillWidth(1)
.Padding(FMargin(2, 0, 6, 0))
.VAlign(VAlign_Center)
[
SNew(STextBlock).Text(InArgs._Text)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(2, 0, 6, 0))
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
[
SNew(SImage).Image(InArgs._ArrowBrush)
]
];
}
/** Compute the desired size for this widget */
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override
{
const float MinWidthVal = MinWidth.Get();
if (MinWidthVal == 0.0f)
{
return SCompoundWidget::ComputeDesiredSize(LayoutScaleMultiplier);
}
else
{
FVector2D ChildSize = ChildSlot.GetWidget()->GetDesiredSize();
return FVector2D(FMath::Max(MinWidthVal, ChildSize.X), ChildSize.Y );
}
}
private:
/** The minimum width of the menu item */
TAttribute<float> MinWidth;
};
/** Feedback widget button that is shown on tab bars */
class SUserFeedbackButtonWidget
: public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SUserFeedbackButtonWidget)
: _Context(){}
SLATE_ARGUMENT(FText, Context)
SLATE_END_ARGS()
/** Construct this widget
*
* @param InArgs The declaration data for this widget
*/
void Construct(const FArguments& InArgs)
{
Context = InArgs._Context;
ChildSlot
[
SAssignNew(ComboButton, SComboButton)
.ButtonStyle(FEditorStyle::Get(), "UserFeedback.Button")
.ToolTipText(LOCTEXT("UserFeedbackToolTip", "Send Quick Feedback"))
.HasDownArrow(false)
.MenuPlacement(EMenuPlacement::MenuPlacement_BelowAnchor)
.OnGetMenuContent(this, &SUserFeedbackButtonWidget::PopulateMenu)
.ContentPadding(0)
.ButtonContent()
[
SNew(SBox)
.WidthOverride(16)
.HeightOverride(16)
]
];
}
private:
void OpenFeedbackPopup(const EFeedbackMode::Type Mode)
{
auto FeedbackWidget = SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Window.Border"))
.Padding(3)
[
SNew(SBox)
.WidthOverride(520.f)
[
SNew(SUserFeedbackWidget)
.Context(Context)
.Mode(Mode)
.OnFeedbackSent(this, &SUserFeedbackButtonWidget::ClosePopupWindow)
.OnCloseClicked(this, &SUserFeedbackButtonWidget::ClosePopupWindow)
]
]
;
auto ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()).ToSharedRef();
// Center ourselves in the parent window
auto PopupWindow = SAssignNew(PopupWindowPtr, SWindow)
.IsPopupWindow(false)
.SizingRule(ESizingRule::Autosized)
.AutoCenter(EAutoCenter::PreferredWorkArea)
.SupportsMaximize(false)
.SupportsMinimize(false)
.FocusWhenFirstShown(true)
.ActivateWhenFirstShown(true)
.Content()
[
FeedbackWidget
];
FSlateApplication::Get().AddWindowAsNativeChild(PopupWindow, ParentWindow, true );
}
/** Close the popup window if it still exists */
void ClosePopupWindow()
{
auto PopupWindow = PopupWindowPtr.Pin();
if (PopupWindow.IsValid())
{
PopupWindow->RequestDestroyWindow();
}
}
/** Populate the menu */
TSharedRef<SWidget> PopulateMenu()
{
const bool bShouldCloseWindowAfterMenuSelection = false;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, MakeShareable(new FUICommandList));
MenuBuilder.AddMenuEntry(LOCTEXT("SendPositiveFeedback", "Send Positive Feedback"), FText(), FSlateIcon(FEditorStyle::GetStyleSetName(), "UserFeedback.PositiveIcon"), FUIAction(FExecuteAction::CreateSP(this, &SUserFeedbackButtonWidget::OpenFeedbackPopup, EFeedbackMode::Positive)));
MenuBuilder.AddMenuEntry(LOCTEXT("SendNegativeFeedback", "Send Negative Feedback"), FText(), FSlateIcon(FEditorStyle::GetStyleSetName(), "UserFeedback.NegativeIcon"), FUIAction(FExecuteAction::CreateSP(this, &SUserFeedbackButtonWidget::OpenFeedbackPopup, EFeedbackMode::Negative)));
MenuBuilder.AddMenuSeparator();
MenuBuilder.AddMenuEntry(LOCTEXT("AskOnUDN", "Ask a question..."), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SUserFeedbackButtonWidget::VisitSupportSite)));
return MenuBuilder.MakeWidget();
}
/** Visit UDN */
void VisitSupportSite()
{
FString SupportWebsiteURL;
if(FUnrealEdMisc::Get().GetURL( TEXT("AskAQuestionURL"), SupportWebsiteURL, true ))
{
FPlatformProcess::LaunchURL( *SupportWebsiteURL, NULL, NULL );
}
}
/** The context to open the popup in (supplied by the client code) */
FText Context;
/** The combo button that opens up the main menu */
TSharedPtr<SComboButton> ComboButton;
/** Weak ptr to the popup window */
TWeakPtr<SWindow> PopupWindowPtr;
};
/** User feedback module, loaded dynamically at startup */
class FUserFeedbackModuleImpl : public IUserFeedbackModule
{
public:
/** Create a widget which allows the user to send positive or negative feedback about a feature */
virtual TSharedRef<SWidget> CreateFeedbackWidget(FText Context) const override
{
if (FEngineAnalytics::IsAvailable() )
{
Slate: Refactored core Slate implementation into SlateCore module in preparation for UMG. Other Updates: - The WidgetReflector is now in its own module as well. It will be converted to a plug-in later. - The Public API of both Slate and SlateCore has largely been reorganized for better discoverabilty. More cleanup work is needed. - Added a lot of missing API documentation and fixed existing ones. More and better documentation is needed. - Removed dead code, fixed a couple things I stubled upon, and conformed to coding guidelines (NULL vs nullptr, line breaks, etc.) Upgrade Notes: - The Slate Remote Server is currently disabled - will be re-enabled shortly! - If your module previously had a module dependency to 'Slate', it now also needs a PrivateModuleDependency to 'SlateCore' in its Build.cs file. - If your module exposes in any of its Public header files types that are now declared in SlateCore, it needs a PublicModuleDependency to 'SlateCore' - The ToolTip property type on SWidget has changed from SToolTip to IToolTip; change local variables to TSharedPtr<IToolTip> instead of TSharedPtr<SToolTip> where needed - IToolTip is not a widget. If you need access to the actual widget that represents the tool tip, use IToolTip::AsWidget(); If you need access to the tool tip's content, use IToolTip::GetContentWidget() Troubleshooting: - After syncing to this changelist you may have to clean your /Engine/Intermediate/Build/ directory and rebuild your entire project - If in your project you are getting linker errors for unresolved types that are now declared in SlateCore, you may be missing a dependency to 'SlateCore' - If in the Engine code you are getting linker errors for unresolved types that are now declared in SlateCore, you may need to rebuild the entire Engine [CL 2057118 by Max Preussner in Main branch]
2014-04-26 15:07:24 -04:00
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0.0f, 0.0f, 0.0f, 0.0f)
.VAlign(VAlign_Center)
[
SNew(SUserFeedbackButtonWidget).Context(Context)
];
}
else
{
return SNullWidget::NullWidget;
}
}
};
IMPLEMENT_MODULE( FUserFeedbackModuleImpl, UserFeedback )
#undef LOCTEXT_NAMESPACE