ExtFeedbackContextEditor.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
  2. #include "ExtFeedbackContextEditor.h"
  3. #include "SExtBuildProgress.h"
  4. #include "HAL/PlatformSplash.h"
  5. #include "Modules/ModuleManager.h"
  6. #include "Fonts/FontMeasure.h"
  7. #include "Framework/Application/SlateApplication.h"
  8. #include "Widgets/Layout/SBorder.h"
  9. #include "Widgets/Notifications/SProgressBar.h"
  10. #include "Widgets/Text/STextBlock.h"
  11. #include "Widgets/Layout/SBox.h"
  12. #include "Widgets/Input/SButton.h"
  13. #include "Styling/CoreStyle.h"
  14. #include "EditorStyleSet.h"
  15. #include "Editor.h"
  16. #include "Interfaces/IMainFrameModule.h"
  17. #include "HAL/PlatformApplicationMisc.h"
  18. #include "Engine/Engine.h"
  19. /** Called to cancel the slow task activity */
  20. DECLARE_DELEGATE( FOnCancelClickedDelegate );
  21. /**
  22. * Simple "slow task" widget
  23. */
  24. class SSlowTaskWidget : public SBorder
  25. {
  26. /** The maximum number of secondary bars to show on the widget */
  27. static const int32 MaxNumSecondaryBars = 3;
  28. /** The width of the dialog, and horizontal padding */
  29. static const int32 FixedWidth = 600, FixedPaddingH = 24;
  30. /** The heights of the progress bars on this widget */
  31. static const int32 MainBarHeight = 12, SecondaryBarHeight = 3;
  32. public:
  33. SLATE_BEGIN_ARGS( SSlowTaskWidget ) { }
  34. /** Called to when an asset is clicked */
  35. SLATE_EVENT( FOnCancelClickedDelegate, OnCancelClickedDelegate )
  36. /** The feedback scope stack that we are presenting to the user */
  37. SLATE_ARGUMENT( TWeakPtr<FSlowTaskStack>, ScopeStack )
  38. SLATE_END_ARGS()
  39. /** Construct this widget */
  40. void Construct( const FArguments& InArgs )
  41. {
  42. OnCancelClickedDelegate = InArgs._OnCancelClickedDelegate;
  43. WeakStack = InArgs._ScopeStack;
  44. // This is a temporary widget that needs to be updated over its entire lifespan => has an active timer registered for its entire lifespan
  45. RegisterActiveTimer( 0.f, FWidgetActiveTimerDelegate::CreateSP( this, &SSlowTaskWidget::UpdateProgress ) );
  46. TSharedRef<SVerticalBox> VerticalBox = SNew(SVerticalBox)
  47. // Construct the main progress bar and text
  48. + SVerticalBox::Slot()
  49. .AutoHeight()
  50. [
  51. SNew(SVerticalBox)
  52. + SVerticalBox::Slot()
  53. .AutoHeight()
  54. .Padding(FMargin(0, 0, 0, 5.f))
  55. .VAlign(VAlign_Center)
  56. [
  57. SNew(SBox)
  58. .HeightOverride(24.f)
  59. [
  60. SNew(SHorizontalBox)
  61. + SHorizontalBox::Slot()
  62. [
  63. SNew( STextBlock )
  64. .AutoWrapText(true)
  65. .Text( this, &SSlowTaskWidget::GetProgressText, 0 )
  66. // The main font size dynamically changes depending on the content
  67. .Font( this, &SSlowTaskWidget::GetMainTextFont )
  68. ]
  69. + SHorizontalBox::Slot()
  70. .Padding(FMargin(5.f, 0, 0, 0))
  71. .AutoWidth()
  72. [
  73. SNew( STextBlock )
  74. .Text( this, &SSlowTaskWidget::GetPercentageText )
  75. // The main font size dynamically changes depending on the content
  76. .Font( FCoreStyle::GetDefaultFontStyle("Light", 14) )
  77. ]
  78. ]
  79. ]
  80. + SVerticalBox::Slot()
  81. .AutoHeight()
  82. [
  83. SNew(SBox)
  84. .HeightOverride(MainBarHeight)
  85. [
  86. SNew(SProgressBar)
  87. .BorderPadding(FVector2D::ZeroVector)
  88. .Percent( this, &SSlowTaskWidget::GetProgressFraction, 0 )
  89. ]
  90. ]
  91. ]
  92. // Secondary progress bars
  93. + SVerticalBox::Slot()
  94. .AutoHeight()
  95. .Padding(FMargin(0.f, 8.f, 0.f, 0.f))
  96. [
  97. SAssignNew(SecondaryBars, SVerticalBox)
  98. ];
  99. if ( OnCancelClickedDelegate.IsBound() )
  100. {
  101. VerticalBox->AddSlot()
  102. .AutoHeight()
  103. .HAlign(HAlign_Center)
  104. .Padding(10.0f, 7.0f)
  105. [
  106. SNew(SButton)
  107. .Text( NSLOCTEXT("FeedbackContextProgress", "Cancel", "Cancel") )
  108. .HAlign(EHorizontalAlignment::HAlign_Center)
  109. .OnClicked(this, &SSlowTaskWidget::OnCancel)
  110. .ClickMethod(EButtonClickMethod::MouseDown)
  111. ];
  112. }
  113. SBorder::Construct( SBorder::FArguments()
  114. .BorderImage(FAppStyle::GetBrush("Menu.Background"))
  115. .VAlign(VAlign_Center)
  116. .Padding(FMargin(FixedPaddingH))
  117. [
  118. SNew(SBox).
  119. WidthOverride(FixedWidth)
  120. [
  121. VerticalBox
  122. ]
  123. ]
  124. );
  125. // Make sure all our bars are set up
  126. UpdateDynamicProgressBars();
  127. }
  128. private:
  129. /** Active timer to update the progress bars */
  130. EActiveTimerReturnType UpdateProgress(double InCurrentTime, float InDeltaTime)
  131. {
  132. UpdateDynamicProgressBars();
  133. return EActiveTimerReturnType::Continue;
  134. }
  135. /** Updates the dynamic progress bars for this widget */
  136. void UpdateDynamicProgressBars()
  137. {
  138. auto ScopeStack = WeakStack.Pin();
  139. if (!ScopeStack.IsValid())
  140. {
  141. return;
  142. }
  143. static const double VisibleScopeThreshold = 0.5;
  144. DynamicProgressIndices.Reset();
  145. // Always show the first one
  146. DynamicProgressIndices.Add(0);
  147. for (int32 Index = 1; Index < ScopeStack->Num() && DynamicProgressIndices.Num() <= MaxNumSecondaryBars - 1; ++Index)
  148. {
  149. const auto* Scope = (*ScopeStack)[Index];
  150. if (Scope->Visibility == ESlowTaskVisibility::ForceVisible)
  151. {
  152. DynamicProgressIndices.Add(Index);
  153. }
  154. else if (Scope->Visibility == ESlowTaskVisibility::Default && !Scope->DefaultMessage.IsEmpty())
  155. {
  156. const auto TimeOpen = FPlatformTime::Seconds() - Scope->StartTime;
  157. const auto WorkDone = ScopeStack->GetProgressFraction(Index);
  158. // We only show visible scopes if they have been opened a while, and have a reasonable amount of work left
  159. if (WorkDone * TimeOpen > VisibleScopeThreshold)
  160. {
  161. DynamicProgressIndices.Add(Index);
  162. }
  163. }
  164. }
  165. // Create progress bars for anything that we haven't cached yet
  166. // We don't destroy old widgets, they just remain ghosted until shown again
  167. for (int32 Index = SecondaryBars->GetChildren()->Num() + 1; Index < DynamicProgressIndices.Num(); ++Index)
  168. {
  169. CreateSecondaryBar(Index);
  170. }
  171. }
  172. /** Create a progress bar for the specified index */
  173. void CreateSecondaryBar(int32 Index)
  174. {
  175. SecondaryBars->AddSlot()
  176. .Padding( 0.f, 16.f, 0.f, 0.f )
  177. [
  178. SNew(SVerticalBox)
  179. .Visibility( this, &SSlowTaskWidget::GetSecondaryBarVisibility, Index )
  180. + SVerticalBox::Slot()
  181. .Padding( 0.f, 0.f, 0.f, 4.f )
  182. .AutoHeight()
  183. [
  184. SNew( STextBlock )
  185. .Text( this, &SSlowTaskWidget::GetProgressText, Index )
  186. .Font( FCoreStyle::GetDefaultFontStyle("Regular", 9) )
  187. .ColorAndOpacity( FSlateColor::UseSubduedForeground() )
  188. ]
  189. + SVerticalBox::Slot()
  190. .AutoHeight()
  191. [
  192. SNew(SBox)
  193. .HeightOverride(SecondaryBarHeight)
  194. [
  195. SNew(SBorder)
  196. .Padding(0)
  197. .BorderImage(FAppStyle::GetBrush("NoBorder"))
  198. .ColorAndOpacity( this, &SSlowTaskWidget::GetSecondaryProgressBarTint, Index )
  199. [
  200. SNew(SProgressBar)
  201. .BorderPadding(FVector2D::ZeroVector)
  202. .Percent( this, &SSlowTaskWidget::GetProgressFraction, Index )
  203. .BackgroundImage( FAppStyle::GetBrush("ProgressBar.ThinBackground") )
  204. .FillImage( FAppStyle::GetBrush("ProgressBar.ThinFill") )
  205. ]
  206. ]
  207. ]
  208. ];
  209. }
  210. private:
  211. /** The main text that we will display in the window */
  212. FText GetPercentageText() const
  213. {
  214. auto ScopeStack = WeakStack.Pin();
  215. if (ScopeStack.IsValid())
  216. {
  217. const float ProgressInterp = ScopeStack->GetProgressFraction(0);
  218. return FText::AsPercent(ProgressInterp);
  219. }
  220. return FText();
  221. }
  222. /** Calculate the best font to display the main text with */
  223. FSlateFontInfo GetMainTextFont() const
  224. {
  225. TSharedRef<FSlateFontMeasure> MeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
  226. const int32 MaxFontSize = 14;
  227. FSlateFontInfo FontInfo = FCoreStyle::GetDefaultFontStyle("Light", MaxFontSize);
  228. const FText MainText = GetProgressText(0);
  229. const int32 MaxTextWidth = FixedWidth - FixedPaddingH*2;
  230. while( FontInfo.Size > 9 && MeasureService->Measure(MainText, FontInfo).X > MaxTextWidth )
  231. {
  232. FontInfo.Size -= 2;
  233. }
  234. return FontInfo;
  235. }
  236. /** Get the tint for a secondary progress bar */
  237. FLinearColor GetSecondaryProgressBarTint(int32 Index) const
  238. {
  239. auto ScopeStack = WeakStack.Pin();
  240. if (ScopeStack.IsValid())
  241. {
  242. if (!DynamicProgressIndices.IsValidIndex(Index) || !ScopeStack->IsValidIndex(DynamicProgressIndices[Index]))
  243. {
  244. return FLinearColor::White.CopyWithNewOpacity(0.25f);
  245. }
  246. }
  247. return FLinearColor::White;
  248. }
  249. /** Get the fractional percentage of completion for a progress bar */
  250. TOptional<float> GetProgressFraction(int32 Index) const
  251. {
  252. auto ScopeStack = WeakStack.Pin();
  253. if (ScopeStack.IsValid())
  254. {
  255. if (DynamicProgressIndices.IsValidIndex(Index) && ScopeStack->IsValidIndex(DynamicProgressIndices[Index]))
  256. {
  257. return ScopeStack->GetProgressFraction(DynamicProgressIndices[Index]);
  258. }
  259. }
  260. return TOptional<float>();
  261. }
  262. /** Get the text to display for a progress bar */
  263. FText GetProgressText(int32 Index) const
  264. {
  265. auto ScopeStack = WeakStack.Pin();
  266. if (ScopeStack.IsValid())
  267. {
  268. if (DynamicProgressIndices.IsValidIndex(Index) && ScopeStack->IsValidIndex(DynamicProgressIndices[Index]))
  269. {
  270. return (*ScopeStack)[DynamicProgressIndices[Index]]->GetCurrentMessage();
  271. }
  272. }
  273. return FText();
  274. }
  275. EVisibility GetSecondaryBarVisibility(int32 Index) const
  276. {
  277. return DynamicProgressIndices.IsValidIndex(Index) ? EVisibility::HitTestInvisible : EVisibility::Collapsed;
  278. }
  279. /** Called when the cancel button is clicked */
  280. FReply OnCancel()
  281. {
  282. OnCancelClickedDelegate.ExecuteIfBound();
  283. return FReply::Handled();
  284. }
  285. private:
  286. /** Delegate to invoke if the user clicks cancel */
  287. FOnCancelClickedDelegate OnCancelClickedDelegate;
  288. /** The scope stack that we are reflecting */
  289. TWeakPtr<FSlowTaskStack> WeakStack;
  290. /** The vertical box containing the secondary progress bars */
  291. TSharedPtr<SVerticalBox> SecondaryBars;
  292. /** Array mapping progress bar index -> scope stack index. Updated every tick. */
  293. TArray<int32> DynamicProgressIndices;
  294. };
  295. /** Static integer definitions required on some builds where the linker needs access to these */
  296. const int32 SSlowTaskWidget::MaxNumSecondaryBars;
  297. const int32 SSlowTaskWidget::FixedWidth;
  298. const int32 SSlowTaskWidget::FixedPaddingH;;
  299. const int32 SSlowTaskWidget::MainBarHeight;
  300. const int32 SSlowTaskWidget::SecondaryBarHeight;
  301. static void TickSlate(TSharedPtr<SWindow> SlowTaskWindow)
  302. {
  303. // Avoid re-entrancy by ticking the active modal window again. This can happen if thhe slow task window is open and a sibling modal window is open as well. We only tick slate if we are the active modal window or a child of the active modal window
  304. if( SlowTaskWindow.IsValid() && ( FSlateApplication::Get().GetActiveModalWindow() == SlowTaskWindow || SlowTaskWindow->IsDescendantOf( FSlateApplication::Get().GetActiveModalWindow() ) ) )
  305. {
  306. // Tick Slate application
  307. FSlateApplication::Get().Tick();
  308. // Sync the game thread and the render thread. This is needed if many StatusUpdate are called
  309. FSlateApplication::Get().GetRenderer()->Sync();
  310. }
  311. }
  312. FExtFeedbackContextEditor::FExtFeedbackContextEditor()
  313. : HasTaskBeenCancelled(false)
  314. {
  315. }
  316. void FExtFeedbackContextEditor::Serialize( const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category )
  317. {
  318. if( !GLog->IsRedirectingTo( this ) )
  319. {
  320. GLog->Serialize( V, Verbosity, Category );
  321. }
  322. }
  323. void FExtFeedbackContextEditor::StartSlowTask( const FText& Task, bool bShowCancelButton )
  324. {
  325. FFeedbackContext::StartSlowTask( Task, bShowCancelButton );
  326. if (GEditor)
  327. {
  328. // reset the cancellation flag
  329. HasTaskBeenCancelled = false;
  330. // If there is a pie window and it is active attempt to parent any slow task dialogs to it to prevent the game window from falling behind due to a slowtask window opening.
  331. TSharedPtr<SWindow> ParentWindow;
  332. if (FWorldContext* PieWorldContext = GEditor->GetPIEWorldContext())
  333. {
  334. FSlatePlayInEditorInfo* SlatePlayInEditorSession = GEditor->SlatePlayInEditorMap.Find(PieWorldContext->ContextHandle);
  335. if (SlatePlayInEditorSession && SlatePlayInEditorSession->SlatePlayInEditorWindow.IsValid())
  336. {
  337. if (FSlateApplication::Get().GetActiveTopLevelWindow() == SlatePlayInEditorSession->SlatePlayInEditorWindow)
  338. {
  339. ParentWindow = SlatePlayInEditorSession->SlatePlayInEditorWindow.Pin();
  340. }
  341. }
  342. }
  343. // Attempt to parent the slow task window to the slate main frame
  344. if (!ParentWindow.IsValid() && FModuleManager::Get().IsModuleLoaded("MainFrame"))
  345. {
  346. IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
  347. ParentWindow = MainFrame.GetParentWindow();
  348. }
  349. if (ParentWindow.IsValid())
  350. {
  351. GSlowTaskOccurred = GIsSlowTask;
  352. // Don't show the progress dialog if the Build Progress dialog is already visible
  353. bool bProgressWindowShown = BuildProgressWidget.IsValid();
  354. // Don't show the progress dialog if a Slate menu is currently open
  355. const bool bHaveOpenMenu = FSlateApplication::Get().AnyMenusVisible();
  356. if (bHaveOpenMenu)
  357. {
  358. UE_LOG(LogSlate, Log, TEXT("Prevented a slow task dialog from being summoned while a context menu was open"));
  359. }
  360. if (!bProgressWindowShown && !bHaveOpenMenu && FSlateApplication::Get().CanDisplayWindows())
  361. {
  362. FOnCancelClickedDelegate OnCancelClicked;
  363. if (bShowCancelButton)
  364. {
  365. // The cancel button is only displayed if a delegate is bound to it.
  366. OnCancelClicked = FOnCancelClickedDelegate::CreateRaw(this, &FExtFeedbackContextEditor::OnUserCancel);
  367. }
  368. const bool bFocusAndActivate = FPlatformApplicationMisc::IsThisApplicationForeground();
  369. TSharedRef<SWindow> SlowTaskWindowRef = SNew(SWindow)
  370. .SizingRule(ESizingRule::Autosized)
  371. .AutoCenter(EAutoCenter::PreferredWorkArea)
  372. .IsPopupWindow(true)
  373. .CreateTitleBar(true)
  374. .ActivationPolicy(bFocusAndActivate ? EWindowActivationPolicy::Always : EWindowActivationPolicy::Never)
  375. .FocusWhenFirstShown(bFocusAndActivate);
  376. SlowTaskWindowRef->SetContent(
  377. SNew(SSlowTaskWidget)
  378. .ScopeStack(GetScopeStackSharedPtr())
  379. .OnCancelClickedDelegate(OnCancelClicked)
  380. );
  381. SlowTaskWindow = SlowTaskWindowRef;
  382. const bool bSlowTask = true;
  383. FSlateApplication::Get().AddModalWindow(SlowTaskWindowRef, ParentWindow, bSlowTask);
  384. SlowTaskWindowRef->ShowWindow();
  385. //SlowTaskStartTime = FStudioAnalytics::GetAnalyticSeconds();
  386. TickSlate(SlowTaskWindow.Pin());
  387. }
  388. FPlatformSplash::SetSplashText(SplashTextType::StartupProgress, *Task.ToString());
  389. }
  390. FPlatformSplash::SetSplashText( SplashTextType::StartupProgress, *Task.ToString() );
  391. }
  392. }
  393. void FExtFeedbackContextEditor::FinalizeSlowTask()
  394. {
  395. auto Window = SlowTaskWindow.Pin();
  396. if (Window.IsValid())
  397. {
  398. Window->SetContent(SNullWidget::NullWidget);
  399. Window->RequestDestroyWindow();
  400. SlowTaskWindow.Reset();
  401. }
  402. FFeedbackContext::FinalizeSlowTask( );
  403. }
  404. void FExtFeedbackContextEditor::ProgressReported( const float TotalProgressInterp, FText DisplayMessage )
  405. {
  406. if (!(FPlatformSplash::IsShown() || BuildProgressWidget.IsValid() || SlowTaskWindow.IsValid()))
  407. {
  408. return;
  409. }
  410. // Clean up deferred cleanup objects from rendering thread every once in a while.
  411. static double LastTimePendingCleanupObjectsWhereDeleted;
  412. if( FPlatformTime::Seconds() - LastTimePendingCleanupObjectsWhereDeleted > 1 )
  413. {
  414. // Get list of objects that are pending cleanup.
  415. FPendingCleanupObjects* PendingCleanupObjects = GetPendingCleanupObjects();
  416. // Flush rendering commands in the queue.
  417. FlushRenderingCommands();
  418. // It is now safe to delete the pending clean objects.
  419. delete PendingCleanupObjects;
  420. // Keep track of time this operation was performed so we don't do it too often.
  421. LastTimePendingCleanupObjectsWhereDeleted = FPlatformTime::Seconds();
  422. }
  423. if (BuildProgressWidget.IsValid() || SlowTaskWindow.IsValid())
  424. {
  425. // CanDisplayWindows can be slow when called repeatedly, so we only call it if a window is open
  426. if (!FSlateApplication::Get().CanDisplayWindows())
  427. {
  428. return;
  429. }
  430. if (BuildProgressWidget.IsValid())
  431. {
  432. if (!DisplayMessage.IsEmpty())
  433. {
  434. BuildProgressWidget->SetBuildStatusText(DisplayMessage);
  435. }
  436. BuildProgressWidget->SetBuildProgressPercent(TotalProgressInterp * 100, 100);
  437. TickSlate(BuildProgressWindow.Pin());
  438. }
  439. else if (SlowTaskWindow.IsValid())
  440. {
  441. TickSlate(SlowTaskWindow.Pin());
  442. }
  443. }
  444. else if (FPlatformSplash::IsShown())
  445. {
  446. // Always show the top-most message
  447. for (auto& Scope : ScopeStack)
  448. {
  449. const FText ThisMessage = Scope->GetCurrentMessage();
  450. if (!ThisMessage.IsEmpty())
  451. {
  452. DisplayMessage = ThisMessage;
  453. break;
  454. }
  455. }
  456. if (!DisplayMessage.IsEmpty())
  457. {
  458. const int32 DotCount = 4;
  459. const float MinTimeBetweenUpdates = 0.2f;
  460. static double LastUpdateTime = -100000.0;
  461. static int32 DotProgress = 0;
  462. const double CurrentTime = FPlatformTime::Seconds();
  463. if( CurrentTime - LastUpdateTime >= MinTimeBetweenUpdates )
  464. {
  465. LastUpdateTime = CurrentTime;
  466. DotProgress = ( DotProgress + 1 ) % DotCount;
  467. }
  468. FString NewDisplayMessage = DisplayMessage.ToString();
  469. NewDisplayMessage.RemoveFromEnd( TEXT( "..." ) );
  470. for( int32 DotIndex = 0; DotIndex <= DotCount; ++DotIndex )
  471. {
  472. if( DotIndex <= DotProgress )
  473. {
  474. NewDisplayMessage.AppendChar( TCHAR( '.' ) );
  475. }
  476. else
  477. {
  478. NewDisplayMessage.AppendChar( TCHAR( ' ' ) );
  479. }
  480. }
  481. NewDisplayMessage.Append( FString::Printf( TEXT( " %i%%" ), int(TotalProgressInterp * 100.f) ) );
  482. DisplayMessage = FText::FromString( NewDisplayMessage );
  483. }
  484. FPlatformSplash::SetSplashText(SplashTextType::StartupProgress, *DisplayMessage.ToString());
  485. }
  486. }
  487. /** Whether or not the user has canceled out of this dialog */
  488. bool FExtFeedbackContextEditor::ReceivedUserCancel( void )
  489. {
  490. return HasTaskBeenCancelled;
  491. }
  492. void FExtFeedbackContextEditor::OnUserCancel()
  493. {
  494. HasTaskBeenCancelled = true;
  495. }
  496. /**
  497. * Show the Build Progress Window
  498. * @return Handle to the Build Progress Widget created
  499. */
  500. TWeakPtr<class SBuildProgressWidget> FExtFeedbackContextEditor::ShowBuildProgressWindow()
  501. {
  502. TSharedRef<SWindow> BuildProgressWindowRef = SNew(SWindow)
  503. .ClientSize(FVector2D(500,200))
  504. .IsPopupWindow(true);
  505. BuildProgressWidget = SNew(SExtBuildProgressWidget);
  506. BuildProgressWindowRef->SetContent( BuildProgressWidget.ToSharedRef() );
  507. BuildProgressWindow = BuildProgressWindowRef;
  508. // Attempt to parent the slow task window to the slate main frame
  509. TSharedPtr<SWindow> ParentWindow;
  510. if( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) )
  511. {
  512. IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>( "MainFrame" );
  513. ParentWindow = MainFrame.GetParentWindow();
  514. }
  515. FSlateApplication::Get().AddModalWindow( BuildProgressWindowRef, ParentWindow, true );
  516. BuildProgressWindowRef->ShowWindow();
  517. BuildProgressWidget->MarkBuildStartTime();
  518. if (FSlateApplication::Get().CanDisplayWindows())
  519. {
  520. TickSlate(BuildProgressWindow.Pin());
  521. }
  522. return BuildProgressWidget;
  523. }
  524. /** Close the Build Progress Window */
  525. void FExtFeedbackContextEditor::CloseBuildProgressWindow()
  526. {
  527. if( BuildProgressWindow.IsValid() && BuildProgressWidget.IsValid())
  528. {
  529. BuildProgressWindow.Pin()->RequestDestroyWindow();
  530. BuildProgressWindow.Reset();
  531. BuildProgressWidget.Reset();
  532. }
  533. }
  534. bool FExtFeedbackContextEditor::IsPlayingInEditor() const
  535. {
  536. return (GIsPlayInEditorWorld || (GEditor && GEditor->PlayWorld != nullptr));
  537. }