int32 SInvalidationPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { if ( GetCanCache() ) { const bool bWasCachingNeeded = bNeedsCaching; if ( bNeedsCaching ) { SInvalidationPanel* MutableThis = const_cast<SInvalidationPanel*>( this ); // Always set the caching flag to false first, during the paint / tick pass we may change something // to volatile and need to re-cache. bNeedsCaching = false; bIsInvalidating = true; if ( !CachedWindowElements.IsValid() || CachedWindowElements->GetWindow() != OutDrawElements.GetWindow() ) { CachedWindowElements = MakeShareable(new FSlateWindowElementList(OutDrawElements.GetWindow())); } else { CachedWindowElements->Reset(); } // Reset the cached node pool index so that we effectively reset the pool. LastUsedCachedNodeIndex = 0; RootCacheNode = CreateCacheNode(); RootCacheNode->Initialize(Args, SharedThis(MutableThis), AllottedGeometry, MyClippingRect); //TODO: When SWidget::Paint is called don't drag self if volatile, and we're doing a cache pass. CachedMaxChildLayer = SCompoundWidget::OnPaint( Args.EnableCaching(SharedThis(MutableThis), RootCacheNode, true, false), AllottedGeometry, MyClippingRect, *CachedWindowElements.Get(), LayerId, InWidgetStyle, bParentEnabled); if ( bCacheRelativeTransforms ) { CachedAbsolutePosition = AllottedGeometry.Position; } LastLayerId = LayerId; LastHitTestIndex = Args.GetLastHitTestIndex(); bIsInvalidating = false; } // The hit test grid is actually populated during the initial cache phase, so don't bother // recording the hit test geometry on the same frame that we regenerate the cache. if ( bWasCachingNeeded == false ) { RootCacheNode->RecordHittestGeometry(Args.GetGrid(), Args.GetLastHitTestIndex()); } if ( bCacheRelativeTransforms ) { FVector2D DeltaPosition = AllottedGeometry.Position - CachedAbsolutePosition; const TArray<FSlateDrawElement>& CachedElements = CachedWindowElements->GetDrawElements(); const int32 CachedElementCount = CachedElements.Num(); for ( int32 Index = 0; Index < CachedElementCount; Index++ ) { const FSlateDrawElement& LocalElement = CachedElements[Index]; FSlateDrawElement AbsElement = LocalElement; AbsElement.SetPosition(LocalElement.GetPosition() + DeltaPosition); AbsElement.SetClippingRect(LocalElement.GetClippingRect().OffsetBy(DeltaPosition)); OutDrawElements.AddItem(AbsElement); } } else { OutDrawElements.AppendDrawElements(CachedWindowElements->GetDrawElements()); } int32 OutMaxChildLayer = CachedMaxChildLayer; // Paint the volatile elements if ( CachedWindowElements.IsValid() ) { OutMaxChildLayer = FMath::Max(CachedMaxChildLayer, CachedWindowElements->PaintVolatile(OutDrawElements)); } #if !UE_BUILD_SHIPPING if ( IsInvalidationDebuggingEnabled() ) { // Draw a green or red border depending on if we were invalidated this frame. { check(Args.IsCaching() == false); //const bool bShowOutlineAsCached = Args.IsCaching() || bWasCachingNeeded == false; const FLinearColor DebugTint = bWasCachingNeeded ? FLinearColor::Red : FLinearColor::Green; FGeometry ScaledOutline = AllottedGeometry.MakeChild(FVector2D(0, 0), AllottedGeometry.GetLocalSize() * AllottedGeometry.Scale, Inverse(AllottedGeometry.Scale)); FSlateDrawElement::MakeBox( OutDrawElements, ++OutMaxChildLayer, ScaledOutline.ToPaintGeometry(), FCoreStyle::Get().GetBrush(TEXT("Debug.Border")), MyClippingRect, ESlateDrawEffect::None, DebugTint ); } // Draw a yellow outline around any volatile elements. const TArray< TSharedRef<FSlateWindowElementList::FVolatilePaint> >& VolatileElements = CachedWindowElements->GetVolatileElements(); for ( const TSharedRef<FSlateWindowElementList::FVolatilePaint>& VolatileElement : VolatileElements ) { FSlateDrawElement::MakeBox( OutDrawElements, ++OutMaxChildLayer, VolatileElement->GetGeometry().ToPaintGeometry(), FCoreStyle::Get().GetBrush(TEXT("FocusRectangle")), MyClippingRect, ESlateDrawEffect::None, FLinearColor::Yellow ); } // Draw a white flash for any widget that invalidated us this frame. for ( TWeakPtr<SWidget> Invalidator : InvalidatorWidgets ) { TSharedPtr<SWidget> SafeInvalidator = Invalidator.Pin(); if ( SafeInvalidator.IsValid() ) { FWidgetPath WidgetPath; if ( FSlateApplication::Get().GeneratePathToWidgetUnchecked(SafeInvalidator.ToSharedRef(), WidgetPath, EVisibility::All) ) { FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(SafeInvalidator.ToSharedRef()).Get(FArrangedWidget::NullWidget); ArrangedWidget.Geometry.AppendTransform( FSlateLayoutTransform(Inverse(Args.GetWindowToDesktopTransform())) ); FSlateDrawElement::MakeBox( OutDrawElements, ++OutMaxChildLayer, ArrangedWidget.Geometry.ToPaintGeometry(), FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")), MyClippingRect, ESlateDrawEffect::None, FLinearColor::White.CopyWithNewOpacity(0.6f) ); } } } InvalidatorWidgets.Reset(); } #endif return OutMaxChildLayer; } else { return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); } }
int32 SWidget::Paint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { INC_DWORD_STAT(STAT_SlateNumPaintedWidgets); SLATE_CYCLE_COUNTER_SCOPE_CUSTOM_DETAILED(SLATE_STATS_DETAIL_LEVEL_MED, GSlateOnPaint, GetType()); // Save the current layout cache we're associated with (if any) LayoutCache = Args.GetLayoutCache(); // Record if we're part of a volatility pass, this is critical for ensuring we don't report a child // of a volatile widget as non-volatile, causing the invalidation panel to do work that's not required. bInheritedVolatility = Args.IsVolatilityPass(); // If this paint pass is to cache off our geometry, but we're a volatile widget, // record this widget as volatile in the draw elements so that we get our own tick/paint // pass later when the layout cache draws. if ( Args.IsCaching() && IsVolatile() ) { const int32 VolatileLayerId = LayerId + 1; OutDrawElements.QueueVolatilePainting( FSlateWindowElementList::FVolatilePaint(SharedThis(this), Args, AllottedGeometry, MyClippingRect, VolatileLayerId, InWidgetStyle, bParentEnabled)); return VolatileLayerId; } if ( bFoldTick && bCanTick ) { FGeometry TickGeometry = AllottedGeometry; TickGeometry.AppendTransform( FSlateLayoutTransform(Args.GetWindowToDesktopTransform()) ); SWidget* MutableThis = const_cast<SWidget*>(this); MutableThis->ExecuteActiveTimers( Args.GetCurrentTime(), Args.GetDeltaTime() ); MutableThis->Tick( TickGeometry, Args.GetCurrentTime(), Args.GetDeltaTime() ); } // Record hit test geometry, but only if we're not caching. const FPaintArgs UpdatedArgs = Args.RecordHittestGeometry(this, AllottedGeometry, MyClippingRect); // Paint the geometry of this widget. int32 NewLayerID = OnPaint(UpdatedArgs, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); // Check if we need to show the keyboard focus ring, this is only necessary if the widget could be focused. if ( bCanSupportFocus && SupportsKeyboardFocus() ) { bool bShowUserFocus = FSlateApplicationBase::Get().ShowUserFocus(SharedThis(this)); if (bShowUserFocus) { const FSlateBrush* BrushResource = GetFocusBrush(); if (BrushResource != nullptr) { FSlateDrawElement::MakeBox( OutDrawElements, NewLayerID, AllottedGeometry.ToPaintGeometry(), BrushResource, MyClippingRect, ESlateDrawEffect::None, BrushResource->GetTint(InWidgetStyle) ); } } } return NewLayerID; }