void FCachedWidgetNode::Initialize(const FPaintArgs& Args, TSharedRef<SWidget> InWidget, const FGeometry& InGeometry, const FSlateRect& InClippingRect) { Widget = InWidget; Geometry = InGeometry; ClippingRect = InClippingRect; WindowOffset = Args.GetWindowToDesktopTransform(); RecordedVisibility = Args.GetLastRecordedVisibility(); LastRecordedHittestIndex = 0; if ( RecordedVisibility.AreChildrenHitTestVisible() ) { RecordedVisibility = InWidget->GetVisibility(); } Children.Reset(); }
/** * This widget was created before render transforms existed for each widget, and it chose to apply the render transform AFTER the layout transform. * This means leveraging the render transform of FGeometry would be expensive, as we would need to use Concat(LayoutTransform, RenderTransform, Inverse(LayoutTransform). * Instead, we maintain the old way of doing it by modifying the AllottedGeometry only during rendering to append the widget's implied RenderTransform to the existing LayoutTransform. */ int32 SFxWidget::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { // Convert the 0..1 origin into local space extents. const FVector2D ScaleOrigin = RenderScaleOrigin.Get() * AllottedGeometry.Size; const FVector2D Offset = VisualOffset.Get() * AllottedGeometry.Size; // create the render transform as a scale around ScaleOrigin and offset it by Offset. const auto RenderTransform = Concatenate(Inverse(ScaleOrigin), RenderScale.Get(), ScaleOrigin, Offset); // This will append the render transform to the layout transform, and we only use it for rendering. FGeometry ModifiedGeometry = AllottedGeometry.MakeChild(AllottedGeometry.Size, RenderTransform); FArrangedChildren ArrangedChildren(EVisibility::Visible); this->ArrangeChildren(ModifiedGeometry, ArrangedChildren); // There may be zero elements in this array if our child collapsed/hidden if( ArrangedChildren.Num() > 0 ) { // We can only have one direct descendant. check( ArrangedChildren.Num() == 1 ); const FArrangedWidget& TheChild = ArrangedChildren[0]; // SFxWidgets are able to ignore parent clipping. const FSlateRect ChildClippingRect = (bIgnoreClipping.Get()) ? ModifiedGeometry.GetClippingRect() : MyClippingRect.IntersectionWith(ModifiedGeometry.GetClippingRect()); FWidgetStyle CompoundedWidgetStyle = FWidgetStyle(InWidgetStyle) .BlendColorAndOpacityTint(ColorAndOpacity.Get()) .SetForegroundColor( ForegroundColor ); return TheChild.Widget->Paint( Args.WithNewParent(this), TheChild.Geometry, ChildClippingRect, OutDrawElements, LayerId + 1, CompoundedWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); } return LayerId; }
int32 SPanel::PaintArrangedChildren( const FPaintArgs& Args, const FArrangedChildren& ArrangedChildren, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { // Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents // wants to an overlay for all of its contents. int32 MaxLayerId = LayerId; const FPaintArgs NewArgs = Args.WithNewParent(this); for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { const FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; bool bWereOverlapping; FSlateRect ChildClipRect = MyClippingRect.IntersectionWith(CurWidget.Geometry.GetClippingRect(), bWereOverlapping); if ( bWereOverlapping ) { const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, ChildClipRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled(bParentEnabled)); MaxLayerId = FMath::Max(MaxLayerId, CurWidgetsMaxLayerId); } } return MaxLayerId; }
/** SWidget Interface */ int32 SSequencerSectionAreaView::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { FArrangedChildren ArrangedChildren(EVisibility::Visible); ArrangeChildren(AllottedGeometry, ArrangedChildren); if( SectionAreaNode.IsValid() ) { // Draw a region around the entire section area FSlateDrawElement::MakeBox( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), BackgroundBrush, MyClippingRect, ESlateDrawEffect::None, SequencerSectionAreaConstants::BackgroundColor ); } for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; FSlateRect ChildClipRect = MyClippingRect.IntersectionWith( CurWidget.Geometry.GetClippingRect() ); const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, ChildClipRect, OutDrawElements, LayerId+1, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); } return LayerId+1; }
int32 SDockingTabWell::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { // When we are dragging a tab, it must be painted on top of the other tabs, so we cannot // just reuse the Panel's default OnPaint. // The TabWell has no visualization of its own; it just visualizes its child tabs. FArrangedChildren ArrangedChildren(EVisibility::Visible); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents // wants to an overlay for all of its contents. int32 MaxLayerId = LayerId; TSharedPtr<SDockTab> ForegroundTab = GetForegroundTab(); FArrangedWidget* ForegroundTabGeometry = NULL; // Draw all inactive tabs first for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; if (CurWidget.Widget == ForegroundTab) { ForegroundTabGeometry = &CurWidget; } else { FSlateRect ChildClipRect = MyClippingRect.IntersectionWith( CurWidget.Geometry.GetClippingRect() ); const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, ChildClipRect, OutDrawElements, MaxLayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId ); } } // Draw active tab in front if (ForegroundTab != TSharedPtr<SDockTab>()) { FSlateRect ChildClipRect = MyClippingRect.IntersectionWith( ForegroundTabGeometry->Geometry.GetClippingRect() ); const int32 CurWidgetsMaxLayerId = ForegroundTabGeometry->Widget->Paint( Args.WithNewParent(this), ForegroundTabGeometry->Geometry, ChildClipRect, OutDrawElements, MaxLayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId ); } return MaxLayerId; }
int32 SGridPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { FArrangedChildren ArrangedChildren(EVisibility::All); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents // wants to an overlay for all of its contents. int32 MaxLayerId = LayerId; // We need to iterate over slots, because slots know the GridLayers. This isn't available in the arranged children. // Some slots do not show up (they are hidden/collapsed). We need a 2nd index to skip over them. // // GridLayers must ensure that everything in LayerN is below LayerN+1. In other words, // every grid layer group must start at the current MaxLayerId (similar to how SOverlay works). int32 LastGridLayer = 0; for (int32 ChildIndex = 0; ChildIndex < Slots.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; if (CurWidget.Widget->GetVisibility().IsVisible()) { const FSlot& CurSlot = Slots[ChildIndex]; FSlateRect ChildClipRect = MyClippingRect.IntersectionWith( CurWidget.Geometry.GetClippingRect() ); if ( LastGridLayer != CurSlot.LayerParam ) { // We starting a new grid layer group? LastGridLayer = CurSlot.LayerParam; // Ensure that everything here is drawn on top of // previously drawn grid content. LayerId = MaxLayerId+1; } const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, ChildClipRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId ); } } //#define LAYOUT_DEBUG #ifdef LAYOUT_DEBUG LayerId = LayoutDebugPaint( AllottedGeometry, MyClippingRect, OutDrawElements, LayerId ); #endif return MaxLayerId; }
int32 SMenuAnchor::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { FArrangedChildren ArrangedChildren( EVisibility::Visible ); ArrangeChildren( AllottedGeometry, ArrangedChildren ); // There may be zero elements in this array if our child collapsed/hidden if ( ArrangedChildren.Num() > 0 ) { const FArrangedWidget& FirstChild = ArrangedChildren[0]; // In the case where the user doesn't provide content to the menu anchor, the null widget // wont appear in the visible set of arranged children, so only immediately paint the first child, // if it's visible and matches the first slot content. const bool bHasArrangedAnchorContent = FirstChild.Widget == Children[0].GetWidget(); if ( bHasArrangedAnchorContent ) { const FSlateRect ChildClippingRect = AllottedGeometry.GetClippingRect().IntersectionWith(MyClippingRect); LayerId = FirstChild.Widget->Paint(Args.WithNewParent(this), FirstChild.Geometry, ChildClippingRect, OutDrawElements, LayerId + 1, InWidgetStyle, ShouldBeEnabled(bParentEnabled)); } const bool bIsOpen = IsOpen(); if ( bIsOpen ) { // In the case where the anchor content is present and visible, it's the 1 index child, in the case // where the anchor content is invisible, it's the 0 index child. FArrangedWidget* PopupChild = nullptr; if ( bHasArrangedAnchorContent && ArrangedChildren.Num() > 1 ) { PopupChild = &ArrangedChildren[1]; } else if ( !bHasArrangedAnchorContent && ArrangedChildren.Num() == 1 ) { PopupChild = &ArrangedChildren[0]; } if ( PopupChild != nullptr ) { OutDrawElements.QueueDeferredPainting( FSlateWindowElementList::FDeferredPaint(PopupChild->Widget, Args, PopupChild->Geometry, MyClippingRect, InWidgetStyle, bParentEnabled)); } } } return LayerId; }
int32 SBox::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { // An SBox just draws its only child FArrangedChildren ArrangedChildren(EVisibility::Visible); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // Maybe none of our children are visible if( ArrangedChildren.Num() > 0 ) { check( ArrangedChildren.Num() == 1 ); FArrangedWidget& TheChild = ArrangedChildren[0]; const FSlateRect ChildClippingRect = AllottedGeometry.GetClippingRect().InsetBy( ChildSlot.SlotPadding.Get() * AllottedGeometry.Scale ).IntersectionWith(MyClippingRect); return TheChild.Widget->Paint( Args.WithNewParent(this), TheChild.Geometry, ChildClippingRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); } return LayerId; }
int32 SConstraintCanvas::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { FArrangedChildren ArrangedChildren(EVisibility::Visible); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents // wants to an overlay for all of its contents. int32 MaxLayerId = LayerId; for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; FSlateRect ChildClipRect = MyClippingRect.IntersectionWith(CurWidget.Geometry.GetClippingRect()); const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, ChildClipRect, OutDrawElements, MaxLayerId + 1, InWidgetStyle, ShouldBeEnabled(bParentEnabled)); MaxLayerId = FMath::Max(MaxLayerId, CurWidgetsMaxLayerId); } return MaxLayerId; }
int32 SSequencerTrackArea::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { FArrangedChildren ArrangedChildren(EVisibility::Visible); ArrangeChildren(AllottedGeometry, ArrangedChildren); for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; FSlateRect ChildClipRect = MyClippingRect.IntersectionWith( CurWidget.Geometry.GetClippingRect() ); const int32 ThisWidgetLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, ChildClipRect, OutDrawElements, LayerId + 1, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); LayerId = FMath::Max(LayerId, ThisWidgetLayerId); } auto SequencerPin = SequencerWidget.Pin(); if (SequencerPin.IsValid()) { return SequencerPin->GetEditTool().OnPaint(AllottedGeometry, MyClippingRect, OutDrawElements, LayerId + 1); } return LayerId; }
int32 SResponsiveGridPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { FArrangedChildren ArrangedChildren(EVisibility::All); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents // wants to an overlay for all of its contents. int32 MaxLayerId = LayerId; // We need to iterate over slots, because slots know the GridLayers. This isn't available in the arranged children. // Some slots do not show up (they are hidden/collapsed). We need a 2nd index to skip over them. for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; if (CurWidget.Widget->GetVisibility().IsVisible()) { FSlateRect ChildClipRect = MyClippingRect.IntersectionWith( CurWidget.Geometry.GetClippingRect() ); const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, ChildClipRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId ); } } #ifdef LAYOUT_DEBUG LayerId = LayoutDebugPaint( AllottedGeometry, MyClippingRect, OutDrawElements, LayerId ); #endif return MaxLayerId; }
int32 SPopup::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { FArrangedChildren ArrangedChildren(EVisibility::Visible); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // There may be zero elements in this array if our child collapsed/hidden if (ArrangedChildren.Num() > 0) { check(ArrangedChildren.Num() == 1); FArrangedWidget& TheChild = ArrangedChildren[0]; FWidgetStyle CompoundedWidgetStyle = FWidgetStyle(InWidgetStyle) .BlendColorAndOpacityTint(ColorAndOpacity.Get()) .SetForegroundColor(GetForegroundColor()); // An SPopup just queues up its children to be painted after everything in this window is done painting. OutDrawElements.QueueDeferredPainting( FSlateWindowElementList::FDeferredPaint(TheChild.Widget, Args.WithNewParent(this), TheChild.Geometry, MyClippingRect, CompoundedWidgetStyle, ShouldBeEnabled(bParentEnabled)) ); } return LayerId; }
int32 SWeakWidget::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { // Just draw the children. FArrangedChildren ArrangedChildren(EVisibility::Visible); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // There may be zero elements in this array if our child collapsed/hidden if( ArrangedChildren.Num() > 0 ) { check( ArrangedChildren.Num() == 1 ); FArrangedWidget& TheChild = ArrangedChildren[0]; return TheChild.Widget->Paint( Args.WithNewParent(this), TheChild.Geometry, MyClippingRect, OutDrawElements, LayerId + 1, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); } return LayerId; }
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 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { #if SLATE_HD_STATS SCOPE_CYCLE_COUNTER( STAT_SlateOnPaint_SGraphPanel ); #endif CachedAllottedGeometryScaledSize = AllottedGeometry.Size * AllottedGeometry.Scale; //Style used for objects that are the same between revisions FWidgetStyle FadedStyle = InWidgetStyle; FadedStyle.BlendColorAndOpacityTint(FLinearColor(0.45f,0.45f,0.45f,0.45f)); // First paint the background const UEditorExperimentalSettings& Options = *GetDefault<UEditorExperimentalSettings>(); const FSlateBrush* BackgroundImage = FEditorStyle::GetBrush(TEXT("Graph.Panel.SolidBackground")); PaintBackgroundAsLines(BackgroundImage, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId); const float ZoomFactor = AllottedGeometry.Scale * GetZoomAmount(); FArrangedChildren ArrangedChildren(EVisibility::Visible); ArrangeChildNodes(AllottedGeometry, ArrangedChildren); // Determine some 'global' settings based on current LOD const bool bDrawShadowsThisFrame = GetCurrentLOD() > EGraphRenderingLOD::LowestDetail; // Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents // wants to an overlay for all of its contents. // Save LayerId for comment boxes to ensure they always appear below nodes & wires const int32 CommentNodeShadowLayerId = LayerId++; const int32 CommentNodeLayerId = LayerId++; // Save a LayerId for wires, which appear below nodes but above comments // We will draw them later, along with the arrows which appear above nodes. const int32 WireLayerId = LayerId++; const int32 NodeShadowsLayerId = LayerId; const int32 NodeLayerId = NodeShadowsLayerId + 1; int32 MaxLayerId = NodeLayerId; const FVector2D NodeShadowSize = GetDefault<UGraphEditorSettings>()->GetShadowDeltaSize(); const UEdGraphSchema* Schema = GraphObj->GetSchema(); // Draw the child nodes { // When drawing a marquee, need a preview of what the selection will be. const FGraphPanelSelectionSet* SelectionToVisualize = &(SelectionManager.SelectedNodes); FGraphPanelSelectionSet SelectionPreview; if ( Marquee.IsValid() ) { ApplyMarqueeSelection(Marquee, SelectionManager.SelectedNodes, SelectionPreview); SelectionToVisualize = &SelectionPreview; } // Context for rendering node infos FKismetNodeInfoContext Context(GraphObj); TArray<FGraphDiffControl::FNodeMatch> NodeMatches; for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(CurWidget.Widget); // Examine node to see what layers we should be drawing in int32 ShadowLayerId = NodeShadowsLayerId; int32 ChildLayerId = NodeLayerId; // If a comment node, draw in the dedicated comment slots { UObject* NodeObj = ChildNode->GetObjectBeingDisplayed(); if (NodeObj && NodeObj->IsA(UEdGraphNode_Comment::StaticClass())) { ShadowLayerId = CommentNodeShadowLayerId; ChildLayerId = CommentNodeLayerId; } } const bool bNodeIsVisible = FSlateRect::DoRectanglesIntersect( CurWidget.Geometry.GetClippingRect(), MyClippingRect ); if (bNodeIsVisible) { const bool bSelected = SelectionToVisualize->Contains( StaticCastSharedRef<SNodePanel::SNode>(CurWidget.Widget)->GetObjectBeingDisplayed() ); // Handle Node renaming once the node is visible if( bSelected && ChildNode->IsRenamePending() ) { ChildNode->ApplyRename(); } // Draw the node's shadow. if (bDrawShadowsThisFrame || bSelected) { const FSlateBrush* ShadowBrush = ChildNode->GetShadowBrush(bSelected); FSlateDrawElement::MakeBox( OutDrawElements, ShadowLayerId, CurWidget.Geometry.ToInflatedPaintGeometry(NodeShadowSize), ShadowBrush, MyClippingRect ); } // Draw the comments and information popups for this node, if it has any. { const SNodePanel::SNode::FNodeSlot& CommentSlot = ChildNode->GetOrAddSlot( ENodeZone::TopCenter ); float CommentBubbleY = -CommentSlot.Offset.Get().Y; Context.bSelected = bSelected; TArray<FGraphInformationPopupInfo> Popups; { ChildNode->GetNodeInfoPopups(&Context, /*out*/ Popups); } for (int32 PopupIndex = 0; PopupIndex < Popups.Num(); ++PopupIndex) { FGraphInformationPopupInfo& Popup = Popups[PopupIndex]; PaintComment(Popup.Message, CurWidget.Geometry, MyClippingRect, OutDrawElements, ChildLayerId, Popup.BackgroundColor, /*inout*/ CommentBubbleY, InWidgetStyle); } } int32 CurWidgetsMaxLayerId; { UEdGraphNode* NodeObj = Cast<UEdGraphNode>(ChildNode->GetObjectBeingDisplayed()); /** When diffing nodes, nodes that are different between revisions are opaque, nodes that have not changed are faded */ FGraphDiffControl::FNodeMatch NodeMatch = FGraphDiffControl::FindNodeMatch(GraphObjToDiff, NodeObj, NodeMatches); if (NodeMatch.IsValid()) { NodeMatches.Add(NodeMatch); } const bool bNodeIsDifferent = (!GraphObjToDiff || NodeMatch.Diff()); /* When dragging off a pin, we want to duck the alpha of some nodes */ TSharedPtr< SGraphPin > OnlyStartPin = (1 == PreviewConnectorFromPins.Num()) ? PreviewConnectorFromPins[0].FindInGraphPanel(*this) : TSharedPtr< SGraphPin >(); const bool bNodeIsNotUsableInCurrentContext = Schema->FadeNodeWhenDraggingOffPin(NodeObj, OnlyStartPin.IsValid() ? OnlyStartPin.Get()->GetPinObj() : NULL); const FWidgetStyle& NodeStyleToUse = (bNodeIsDifferent && !bNodeIsNotUsableInCurrentContext)? InWidgetStyle : FadedStyle; // Draw the node.O CurWidgetsMaxLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, MyClippingRect, OutDrawElements, ChildLayerId, NodeStyleToUse, ShouldBeEnabled( bParentEnabled ) ); } // Draw the node's overlay, if it has one. { // Get its size const FVector2D WidgetSize = CurWidget.Geometry.Size; { TArray<FOverlayBrushInfo> OverlayBrushes; ChildNode->GetOverlayBrushes(bSelected, WidgetSize, /*out*/ OverlayBrushes); for (int32 BrushIndex = 0; BrushIndex < OverlayBrushes.Num(); ++BrushIndex) { FOverlayBrushInfo& OverlayInfo = OverlayBrushes[BrushIndex]; const FSlateBrush* OverlayBrush = OverlayInfo.Brush; if(OverlayBrush != NULL) { FPaintGeometry BouncedGeometry = CurWidget.Geometry.ToPaintGeometry(OverlayInfo.OverlayOffset, OverlayBrush->ImageSize, 1.f); // Handle bouncing const float BounceValue = FMath::Sin(2.0f * PI * BounceCurve.GetLerpLooping()); BouncedGeometry.DrawPosition += (OverlayInfo.AnimationEnvelope * BounceValue * ZoomFactor); CurWidgetsMaxLayerId++; FSlateDrawElement::MakeBox( OutDrawElements, CurWidgetsMaxLayerId, BouncedGeometry, OverlayBrush, MyClippingRect ); } } } { TArray<FOverlayWidgetInfo> OverlayWidgets = ChildNode->GetOverlayWidgets(bSelected, WidgetSize); for (int32 WidgetIndex = 0; WidgetIndex < OverlayWidgets.Num(); ++WidgetIndex) { FOverlayWidgetInfo& OverlayInfo = OverlayWidgets[WidgetIndex]; if(OverlayInfo.Widget->GetVisibility() == EVisibility::Visible) { // call SlatePrepass as these widgets are not in the 'normal' child hierarchy OverlayInfo.Widget->SlatePrepass(); const FGeometry WidgetGeometry = CurWidget.Geometry.MakeChild(OverlayInfo.OverlayOffset, OverlayInfo.Widget->GetDesiredSize(), 1.f); OverlayInfo.Widget->Paint(Args.WithNewParent(this), WidgetGeometry, MyClippingRect, OutDrawElements, CurWidgetsMaxLayerId, InWidgetStyle, bParentEnabled); } } } } MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId + 1 ); } } } MaxLayerId += 1; // Draw connections between pins if (Children.Num() > 0 ) { //@TODO: Pull this into a factory like the pin and node ones FConnectionDrawingPolicy* ConnectionDrawingPolicy; { ConnectionDrawingPolicy = Schema->CreateConnectionDrawingPolicy(WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements, GraphObj); if (!ConnectionDrawingPolicy) { if (Schema->IsA(UAnimationGraphSchema::StaticClass())) { ConnectionDrawingPolicy = new FAnimGraphConnectionDrawingPolicy(WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements, GraphObj); } else if (Schema->IsA(UAnimationStateMachineSchema::StaticClass())) { ConnectionDrawingPolicy = new FStateMachineConnectionDrawingPolicy(WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements, GraphObj); } else if (Schema->IsA(UEdGraphSchema_K2::StaticClass())) { ConnectionDrawingPolicy = new FKismetConnectionDrawingPolicy(WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements, GraphObj); } else if (Schema->IsA(USoundCueGraphSchema::StaticClass())) { ConnectionDrawingPolicy = new FSoundCueGraphConnectionDrawingPolicy(WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements, GraphObj); } else if (Schema->IsA(UMaterialGraphSchema::StaticClass())) { ConnectionDrawingPolicy = new FMaterialGraphConnectionDrawingPolicy(WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements, GraphObj); } else { ConnectionDrawingPolicy = new FConnectionDrawingPolicy(WireLayerId, MaxLayerId, ZoomFactor, MyClippingRect, OutDrawElements); } } } TArray<TSharedPtr<SGraphPin>> OverridePins; for (const FGraphPinHandle& Handle : PreviewConnectorFromPins) { TSharedPtr<SGraphPin> Pin = Handle.FindInGraphPanel(*this); if (Pin.IsValid()) { OverridePins.Add(Pin); } } ConnectionDrawingPolicy->SetHoveredPins(CurrentHoveredPins, OverridePins, TimeSinceMouseEnteredPin); ConnectionDrawingPolicy->SetMarkedPin(MarkedPin); // Get the set of pins for all children and synthesize geometry for culled out pins so lines can be drawn to them. TMap<TSharedRef<SWidget>, FArrangedWidget> PinGeometries; TSet< TSharedRef<SWidget> > VisiblePins; for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex) { TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(Children[ChildIndex]); // If this is a culled node, approximate the pin geometry to the corner of the node it is within if (IsNodeCulled(ChildNode, AllottedGeometry)) { TArray< TSharedRef<SWidget> > NodePins; ChildNode->GetPins(NodePins); const FVector2D NodeLoc = ChildNode->GetPosition(); const FGeometry SynthesizedNodeGeometry(GraphCoordToPanelCoord(NodeLoc), AllottedGeometry.AbsolutePosition, FVector2D::ZeroVector, 1.f); for (TArray< TSharedRef<SWidget> >::TConstIterator NodePinIterator(NodePins); NodePinIterator; ++NodePinIterator) { const SGraphPin& PinWidget = static_cast<const SGraphPin&>((*NodePinIterator).Get()); FVector2D PinLoc = NodeLoc + PinWidget.GetNodeOffset(); const FGeometry SynthesizedPinGeometry(GraphCoordToPanelCoord(PinLoc), AllottedGeometry.AbsolutePosition, FVector2D::ZeroVector, 1.f); PinGeometries.Add(*NodePinIterator, FArrangedWidget(*NodePinIterator, SynthesizedPinGeometry)); } // Also add synthesized geometries for culled nodes ArrangedChildren.AddWidget( FArrangedWidget(ChildNode, SynthesizedNodeGeometry) ); } else { ChildNode->GetPins(VisiblePins); } } // Now get the pin geometry for all visible children and append it to the PinGeometries map TMap<TSharedRef<SWidget>, FArrangedWidget> VisiblePinGeometries; { this->FindChildGeometries(AllottedGeometry, VisiblePins, VisiblePinGeometries); PinGeometries.Append(VisiblePinGeometries); } // Draw preview connections (only connected on one end) if (PreviewConnectorFromPins.Num() > 0) { for (const FGraphPinHandle& Handle : PreviewConnectorFromPins) { TSharedPtr< SGraphPin > CurrentStartPin = Handle.FindInGraphPanel(*this); if (!CurrentStartPin.IsValid()) { continue; } const FArrangedWidget* PinGeometry = PinGeometries.Find( CurrentStartPin.ToSharedRef() ); if (PinGeometry != NULL) { FVector2D StartPoint; FVector2D EndPoint; if (CurrentStartPin->GetDirection() == EGPD_Input) { StartPoint = AllottedGeometry.AbsolutePosition + PreviewConnectorEndpoint; EndPoint = FGeometryHelper::VerticalMiddleLeftOf( PinGeometry->Geometry ) - FVector2D(ConnectionDrawingPolicy->ArrowRadius.X, 0); } else { StartPoint = FGeometryHelper::VerticalMiddleRightOf( PinGeometry->Geometry ); EndPoint = AllottedGeometry.AbsolutePosition + PreviewConnectorEndpoint; } ConnectionDrawingPolicy->DrawPreviewConnector(PinGeometry->Geometry, StartPoint, EndPoint, CurrentStartPin.Get()->GetPinObj()); } //@TODO: Re-evaluate this incompatible mojo; it's mutating every pin state every frame to accomplish a visual effect ConnectionDrawingPolicy->SetIncompatiblePinDrawState(CurrentStartPin, VisiblePins); } } else { //@TODO: Re-evaluate this incompatible mojo; it's mutating every pin state every frame to accomplish a visual effect ConnectionDrawingPolicy->ResetIncompatiblePinDrawState(VisiblePins); } // Draw all regular connections ConnectionDrawingPolicy->Draw(PinGeometries, ArrangedChildren); delete ConnectionDrawingPolicy; } // Draw a shadow overlay around the edges of the graph ++MaxLayerId; PaintSurroundSunkenShadow(FEditorStyle::GetBrush(TEXT("Graph.Shadow")), AllottedGeometry, MyClippingRect, OutDrawElements, MaxLayerId); if(ShowGraphStateOverlay.Get()) { const FSlateBrush* BorderBrush = nullptr; if((GEditor->bIsSimulatingInEditor || GEditor->PlayWorld != NULL)) { // Draw a surrounding indicator when PIE is active, to make it clear that the graph is read-only, etc... BorderBrush = FEditorStyle::GetBrush(TEXT("Graph.PlayInEditor")); } else if(!IsEditable.Get()) { // Draw a different border when we're not simulating but the graph is read-only BorderBrush = FEditorStyle::GetBrush(TEXT("Graph.ReadOnlyBorder")); } if(BorderBrush) { // Actually draw the border FSlateDrawElement::MakeBox( OutDrawElements, MaxLayerId, AllottedGeometry.ToPaintGeometry(), BorderBrush, MyClippingRect ); } } // Draw the marquee selection rectangle PaintMarquee(AllottedGeometry, MyClippingRect, OutDrawElements, MaxLayerId); // Draw the software cursor ++MaxLayerId; PaintSoftwareCursor(AllottedGeometry, MyClippingRect, OutDrawElements, MaxLayerId); return MaxLayerId; }
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; }
/** SWidget interface */ virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override { // First paint the background { LayerId = PaintBackground(AllottedGeometry, MyClippingRect, OutDrawElements, LayerId); LayerId++; } FArrangedChildren ArrangedChildren(EVisibility::Visible); ArrangeChildren(AllottedGeometry, ArrangedChildren); // Draw the child nodes // When drawing a marquee, need a preview of what the selection will be. const auto* SelectionToVisualize = &(SelectionManager.SelectedNodes); FGraphPanelSelectionSet SelectionPreview; if (Marquee.IsValid()) { ApplyMarqueeSelection(Marquee, SelectionManager.SelectedNodes, SelectionPreview); SelectionToVisualize = &SelectionPreview; } int32 NodesLayerId = LayerId; for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; TSharedRef<SWorldTileItem> ChildNode = StaticCastSharedRef<SWorldTileItem>(CurWidget.Widget); ChildNode->bAffectedByMarquee = SelectionToVisualize->Contains(ChildNode->GetObjectBeingDisplayed()); LayerId = CurWidget.Widget->Paint(Args.WithNewParent(this), CurWidget.Geometry, MyClippingRect, OutDrawElements, NodesLayerId, InWidgetStyle, ShouldBeEnabled(bParentEnabled)); ChildNode->bAffectedByMarquee = false; } // Draw editable world bounds if (!WorldModel->IsSimulating()) { float ScreenSpaceSize = FLevelCollectionModel::EditableAxisLength()*GetZoomAmount()*2.f; FVector2D PaintSize = FVector2D(ScreenSpaceSize, ScreenSpaceSize); FVector2D PaintPosition = GraphCoordToPanelCoord(FVector2D::ZeroVector) - (PaintSize*0.5f); float Scale = 0.2f; // Scale down drawing border FSlateLayoutTransform LayoutTransform(Scale, AllottedGeometry.GetAccumulatedLayoutTransform().GetTranslation() + PaintPosition); FSlateRenderTransform RenderTransform(Scale, AllottedGeometry.GetAccumulatedRenderTransform().GetTranslation() + PaintPosition); FPaintGeometry EditableArea(LayoutTransform, RenderTransform, PaintSize/Scale); FLinearColor PaintColor = FLinearColor::Yellow; PaintColor.A = 0.4f; FSlateDrawElement::MakeBox( OutDrawElements, ++LayerId, EditableArea, FEditorStyle::GetBrush(TEXT("Graph.CompactNode.ShadowSelected")), MyClippingRect, ESlateDrawEffect::None, PaintColor ); } // Draw the marquee selection rectangle PaintMarquee(AllottedGeometry, MyClippingRect, OutDrawElements, ++LayerId); // Draw the software cursor PaintSoftwareCursor(AllottedGeometry, MyClippingRect, OutDrawElements, ++LayerId); if(WorldModel->IsSimulating()) { // Draw a surrounding indicator when PIE is active, to make it clear that the graph is read-only, etc... FSlateDrawElement::MakeBox( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), FEditorStyle::GetBrush(TEXT("Graph.PlayInEditor")), MyClippingRect ); } // Draw observer location { FVector ObserverPosition; FRotator ObserverRotation; if (WorldModel->GetObserverView(ObserverPosition, ObserverRotation)) { FVector2D ObserverPositionScreen = GraphCoordToPanelCoord(FVector2D(ObserverPosition.X, ObserverPosition.Y)); const FSlateBrush* CameraImage = FEditorStyle::GetBrush(TEXT("WorldBrowser.SimulationViewPositon")); FPaintGeometry PaintGeometry = AllottedGeometry.ToPaintGeometry( ObserverPositionScreen - CameraImage->ImageSize*0.5f, CameraImage->ImageSize ); FSlateDrawElement::MakeRotatedBox( OutDrawElements, ++LayerId, PaintGeometry, CameraImage, MyClippingRect, ESlateDrawEffect::None, FMath::DegreesToRadians(ObserverRotation.Yaw), CameraImage->ImageSize*0.5f, FSlateDrawElement::RelativeToElement ); } FVector PlayerPosition; FRotator PlayerRotation; if (WorldModel->GetPlayerView(PlayerPosition, PlayerRotation)) { FVector2D PlayerPositionScreen = GraphCoordToPanelCoord(FVector2D(PlayerPosition.X, PlayerPosition.Y)); const FSlateBrush* CameraImage = FEditorStyle::GetBrush(TEXT("WorldBrowser.SimulationViewPositon")); FPaintGeometry PaintGeometry = AllottedGeometry.ToPaintGeometry( PlayerPositionScreen - CameraImage->ImageSize*0.5f, CameraImage->ImageSize ); FSlateDrawElement::MakeRotatedBox( OutDrawElements, ++LayerId, PaintGeometry, CameraImage, MyClippingRect, ESlateDrawEffect::None, FMath::DegreesToRadians(PlayerRotation.Yaw), CameraImage->ImageSize*0.5f, FSlateDrawElement::RelativeToElement, FLinearColor(FColorList::Orange) ); } } LayerId = PaintScaleRuler(AllottedGeometry, MyClippingRect, OutDrawElements, LayerId); return LayerId; }