// Copyright Epic Games, Inc. All Rights Reserved. #include "Designer/SDesignSurface.h" #include "Rendering/DrawElements.h" #include "Framework/Application/SlateApplication.h" #if WITH_EDITOR #include "Settings/EditorStyleSettings.h" #include "Settings/LevelEditorViewportSettings.h" #endif // WITH_EDITOR #define LOCTEXT_NAMESPACE "UMG" struct FZoomLevelEntry { public: FZoomLevelEntry(float InZoomAmount, const FText& InDisplayText, EGraphRenderingLOD::Type InLOD) : DisplayText(FText::Format(LOCTEXT("Zoom", "Zoom {0}"), InDisplayText)) , ZoomAmount(InZoomAmount) , LOD(InLOD) { } public: FText DisplayText; float ZoomAmount; EGraphRenderingLOD::Type LOD; }; struct FFixedZoomLevelsContainerDesignSurface : public FZoomLevelsContainer { FFixedZoomLevelsContainerDesignSurface() { ZoomLevels.Add(FZoomLevelEntry(0.150f, FText::FromString("-10"), EGraphRenderingLOD::LowestDetail)); ZoomLevels.Add(FZoomLevelEntry(0.175f, FText::FromString("-9"), EGraphRenderingLOD::LowestDetail)); ZoomLevels.Add(FZoomLevelEntry(0.200f, FText::FromString("-8"), EGraphRenderingLOD::LowestDetail)); ZoomLevels.Add(FZoomLevelEntry(0.225f, FText::FromString("-7"), EGraphRenderingLOD::LowDetail)); ZoomLevels.Add(FZoomLevelEntry(0.250f, FText::FromString("-6"), EGraphRenderingLOD::LowDetail)); ZoomLevels.Add(FZoomLevelEntry(0.375f, FText::FromString("-5"), EGraphRenderingLOD::MediumDetail)); ZoomLevels.Add(FZoomLevelEntry(0.500f, FText::FromString("-4"), EGraphRenderingLOD::MediumDetail)); ZoomLevels.Add(FZoomLevelEntry(0.675f, FText::FromString("-3"), EGraphRenderingLOD::MediumDetail)); ZoomLevels.Add(FZoomLevelEntry(0.750f, FText::FromString("-2"), EGraphRenderingLOD::DefaultDetail)); ZoomLevels.Add(FZoomLevelEntry(0.875f, FText::FromString("-1"), EGraphRenderingLOD::DefaultDetail)); ZoomLevels.Add(FZoomLevelEntry(1.000f, FText::FromString("1:1"), EGraphRenderingLOD::DefaultDetail)); ZoomLevels.Add(FZoomLevelEntry(1.250f, FText::FromString("+1"), EGraphRenderingLOD::DefaultDetail)); ZoomLevels.Add(FZoomLevelEntry(1.500f, FText::FromString("+2"), EGraphRenderingLOD::DefaultDetail)); ZoomLevels.Add(FZoomLevelEntry(1.750f, FText::FromString("+3"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(2.000f, FText::FromString("+4"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(2.250f, FText::FromString("+5"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(2.500f, FText::FromString("+6"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(2.750f, FText::FromString("+7"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(3.000f, FText::FromString("+8"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(3.250f, FText::FromString("+9"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(3.500f, FText::FromString("+10"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(4.000f, FText::FromString("+11"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(5.000f, FText::FromString("+12"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(6.000f, FText::FromString("+13"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(7.000f, FText::FromString("+14"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(8.000f, FText::FromString("+15"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(9.000f, FText::FromString("+16"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(10.000f, FText::FromString("+17"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(11.000f, FText::FromString("+18"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(12.000f, FText::FromString("+19"), EGraphRenderingLOD::FullyZoomedIn)); ZoomLevels.Add(FZoomLevelEntry(13.000f, FText::FromString("+20"), EGraphRenderingLOD::FullyZoomedIn)); } float GetZoomAmount(int32 InZoomLevel) const override { checkSlow(ZoomLevels.IsValidIndex(InZoomLevel)); return ZoomLevels[InZoomLevel].ZoomAmount; } int32 GetNearestZoomLevel(float InZoomAmount) const override { for ( int32 ZoomLevelIndex=0; ZoomLevelIndex < GetNumZoomLevels(); ++ZoomLevelIndex ) { if ( InZoomAmount <= GetZoomAmount(ZoomLevelIndex) ) { return ZoomLevelIndex; } } return GetDefaultZoomLevel(); } FText GetZoomText(int32 InZoomLevel) const override { checkSlow(ZoomLevels.IsValidIndex(InZoomLevel)); return ZoomLevels[InZoomLevel].DisplayText; } int32 GetNumZoomLevels() const override { return ZoomLevels.Num(); } int32 GetDefaultZoomLevel() const override { return 10; } EGraphRenderingLOD::Type GetLOD(int32 InZoomLevel) const override { checkSlow(ZoomLevels.IsValidIndex(InZoomLevel)); return ZoomLevels[InZoomLevel].LOD; } TArray ZoomLevels; }; ///////////////////////////////////////////////////// // SDesignSurface void SDesignSurface::Construct(const FArguments& InArgs) { if ( !ZoomLevels ) { ZoomLevels = MakeUnique(); } ZoomLevel = ZoomLevels->GetDefaultZoomLevel(); PreviousZoomLevel = ZoomLevels->GetDefaultZoomLevel(); PostChangedZoom(); AllowContinousZoomInterpolation = InArgs._AllowContinousZoomInterpolation; bIsPanning = false; bIsZooming = false; ViewOffset = FVector2D::ZeroVector; bDrawGridLines = true; ZoomLevelFade = FCurveSequence(0.0f, 1.0f); ZoomLevelFade.Play( this->AsShared() ); ZoomLevelGraphFade = FCurveSequence(0.0f, 0.5f); ZoomLevelGraphFade.Play( this->AsShared() ); bDeferredZoomToExtents = false; bAllowContinousZoomInterpolation = false; bTeleportInsteadOfScrollingWhenZoomingToFit = false; bRequireControlToOverZoom = false; ZoomTargetTopLeft = FVector2D::ZeroVector; ZoomTargetBottomRight = FVector2D::ZeroVector; ZoomToFitPadding = FVector2D(100, 100); TotalGestureMagnify = 0.0f; TotalMouseDelta = 0.0f; ZoomStartOffset = FVector2D::ZeroVector; ChildSlot [ InArgs._Content.Widget ]; } EActiveTimerReturnType SDesignSurface::HandleZoomToFit( double InCurrentTime, float InDeltaTime ) { const FVector2D DesiredViewCenter = ( ZoomTargetTopLeft + ZoomTargetBottomRight ) * 0.5f; const bool bDoneScrolling = ScrollToLocation(GetCachedGeometry(), DesiredViewCenter, bTeleportInsteadOfScrollingWhenZoomingToFit ? 1000.0f : InDeltaTime); const bool bDoneZooming = ZoomToLocation(GetCachedGeometry().GetLocalSize(), ZoomTargetBottomRight - ZoomTargetTopLeft, bDoneScrolling); if (bDoneZooming && bDoneScrolling) { // One final push to make sure we're centered in the end ViewOffset = DesiredViewCenter - ( 0.5f * GetCachedGeometry().GetLocalSize() / GetZoomAmount() ); ZoomTargetTopLeft = FVector2D::ZeroVector; ZoomTargetBottomRight = FVector2D::ZeroVector; return EActiveTimerReturnType::Stop; } return EActiveTimerReturnType::Continue; } void SDesignSurface::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { // Zoom to extents FSlateRect Bounds = ComputeAreaBounds(); if ( bDeferredZoomToExtents ) { bDeferredZoomToExtents = false; ZoomTargetTopLeft = FVector2D(Bounds.Left, Bounds.Top); ZoomTargetBottomRight = FVector2D(Bounds.Right, Bounds.Bottom); if (!ActiveTimerHandle.IsValid()) { RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SDesignSurface::HandleZoomToFit)); } } } FCursorReply SDesignSurface::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const { if ( bIsPanning ) { return FCursorReply::Cursor(EMouseCursor::GrabHand); } return SCompoundWidget::OnCursorQuery(MyGeometry, CursorEvent); } int32 SDesignSurface::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { // Store the current Scene Index, as child classes may want to use a different scene to preview widgets (Ex: SDesignerView) int32 SceneIndex = FSlateApplication::Get().GetRenderer()->GetCurrentSceneIndex(); OnPaintBackground(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId); int32 Result = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); // Restore previous Scene Index to avoid permanently changing the scene FSlateApplication::Get().GetRenderer()->SetCurrentSceneIndex(SceneIndex); return Result; } void SDesignSurface::OnPaintBackground(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { const FSlateBrush* BackgroundImage = FAppStyle::GetBrush(TEXT("Graph.Panel.SolidBackground")); PaintBackgroundAsLines(BackgroundImage, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId); } FReply SDesignSurface::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { SCompoundWidget::OnMouseButtonDown(MyGeometry, MouseEvent); if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton || MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton ) { bIsPanning = false; ViewOffsetStart = ViewOffset; MouseDownPositionAbsolute = MouseEvent.GetLastScreenSpacePosition(); } if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton || FSlateApplication::Get().IsUsingTrackpad()) { TotalMouseDelta = 0.0f; ZoomStartOffset = MyGeometry.AbsoluteToLocal(MouseEvent.GetLastScreenSpacePosition()); } return FReply::Unhandled(); } FReply SDesignSurface::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { SCompoundWidget::OnMouseButtonUp(MyGeometry, MouseEvent); if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton || MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton ) { bIsPanning = false; bIsZooming = false; } return FReply::Unhandled(); } FReply SDesignSurface::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { const bool bIsRightMouseButtonDown = MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton); const bool bIsLeftMouseButtonDown = MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton); const bool bIsMiddleMouseButtonDown = MouseEvent.IsMouseButtonDown(EKeys::MiddleMouseButton); const FModifierKeysState ModifierKeysState = FSlateApplication::Get().GetModifierKeys(); if ( HasMouseCapture() ) { const FVector2D CursorDelta = MouseEvent.GetCursorDelta(); const bool bShouldZoom = bIsRightMouseButtonDown && (bIsLeftMouseButtonDown || bIsMiddleMouseButtonDown || ModifierKeysState.IsAltDown() || FSlateApplication::Get().IsUsingTrackpad()); if ( bShouldZoom ) { const double MouseZoomScaling = 0.04f; FReply ReplyState = FReply::Handled(); TotalMouseDelta += CursorDelta.X + CursorDelta.Y; const int32 ZoomLevelDelta = FMath::RoundToInt32(TotalMouseDelta * MouseZoomScaling); // Get rid of mouse movement that's been 'used up' by zooming if (ZoomLevelDelta != 0) { TotalMouseDelta -= (ZoomLevelDelta / MouseZoomScaling); bIsZooming = true; } // Perform zoom centered on the cached start offset ChangeZoomLevel(ZoomLevelDelta, ZoomStartOffset, MouseEvent.IsControlDown()); bIsPanning = false; return ReplyState; } else if ( bIsRightMouseButtonDown || bIsMiddleMouseButtonDown ) { FReply ReplyState = FReply::Handled(); bIsPanning = true; bIsZooming = false; ViewOffset = ViewOffsetStart + ( (MouseDownPositionAbsolute - MouseEvent.GetScreenSpacePosition()) / MyGeometry.Scale) / GetZoomAmount(); return ReplyState; } } return FReply::Unhandled(); } FReply SDesignSurface::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { // We want to zoom into this point; i.e. keep it the same fraction offset into the panel const FVector2D WidgetSpaceCursorPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); const int32 ZoomLevelDelta = FMath::FloorToInt(MouseEvent.GetWheelDelta()); ChangeZoomLevel(ZoomLevelDelta, WidgetSpaceCursorPos, !bRequireControlToOverZoom || MouseEvent.IsControlDown()); MouseDownPositionAbsolute = MouseEvent.GetScreenSpacePosition(); return FReply::Handled(); } FReply SDesignSurface::OnTouchGesture(const FGeometry& MyGeometry, const FPointerEvent& GestureEvent) { const EGestureEvent GestureType = GestureEvent.GetGestureType(); const FVector2D GestureDelta = GestureEvent.GetGestureDelta(); if ( GestureType == EGestureEvent::Magnify ) { TotalGestureMagnify += static_cast(GestureDelta.X); if ( FMath::Abs(TotalGestureMagnify) > 0.07f ) { // We want to zoom into this point; i.e. keep it the same fraction offset into the panel const FVector2D WidgetSpaceCursorPos = MyGeometry.AbsoluteToLocal(GestureEvent.GetScreenSpacePosition()); const int32 ZoomLevelDelta = TotalGestureMagnify > 0.0f ? 1 : -1; ChangeZoomLevel(ZoomLevelDelta, WidgetSpaceCursorPos, !bRequireControlToOverZoom || GestureEvent.IsControlDown()); MouseDownPositionAbsolute = GestureEvent.GetScreenSpacePosition(); TotalGestureMagnify = 0.0f; } return FReply::Handled(); } else if ( GestureType == EGestureEvent::Scroll ) { const EScrollGestureDirection DirectionSetting = GetDefault()->ScrollGestureDirectionForOrthoViewports; const bool bUseDirectionInvertedFromDevice = DirectionSetting == EScrollGestureDirection::Natural || (DirectionSetting == EScrollGestureDirection::UseSystemSetting && GestureEvent.IsDirectionInvertedFromDevice()); this->bIsPanning = true; ViewOffset -= (bUseDirectionInvertedFromDevice == GestureEvent.IsDirectionInvertedFromDevice() ? GestureDelta : -GestureDelta) / GetZoomAmount(); return FReply::Handled(); } return FReply::Unhandled(); } FReply SDesignSurface::OnTouchEnded(const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent) { TotalGestureMagnify = 0.0f; return FReply::Unhandled(); } inline float FancyMod(float Value, float Size) { return ( ( Value >= 0 ) ? 0.0f : Size ) + FMath::Fmod(Value, Size); } float SDesignSurface::GetZoomAmount() const { if ( AllowContinousZoomInterpolation.Get() ) { return FMath::Lerp(ZoomLevels->GetZoomAmount(PreviousZoomLevel), ZoomLevels->GetZoomAmount(ZoomLevel), ZoomLevelGraphFade.GetLerp()); } else { return ZoomLevels->GetZoomAmount(ZoomLevel); } } void SDesignSurface::ChangeZoomLevel(int32 ZoomLevelDelta, const FVector2D& WidgetSpaceZoomOrigin, bool bOverrideZoomLimiting) { // We want to zoom into this point; i.e. keep it the same fraction offset into the panel const FVector2D PointToMaintainGraphSpace = PanelCoordToGraphCoord(WidgetSpaceZoomOrigin); const int32 DefaultZoomLevel = ZoomLevels->GetDefaultZoomLevel(); const int32 NumZoomLevels = ZoomLevels->GetNumZoomLevels(); const bool bAllowFullZoomRange = // To zoom in past 1:1 the user must press control ( ZoomLevel == DefaultZoomLevel && ZoomLevelDelta > 0 && bOverrideZoomLimiting ) || // If they are already zoomed in past 1:1, user may zoom freely ( ZoomLevel > DefaultZoomLevel ); const int32 OldZoomLevel = ZoomLevel; if ( bAllowFullZoomRange ) { ZoomLevel = FMath::Clamp(ZoomLevel + ZoomLevelDelta, 0, NumZoomLevels - 1); } else { // Without control, we do not allow zooming in past 1:1. ZoomLevel = FMath::Clamp(ZoomLevel + ZoomLevelDelta, 0, DefaultZoomLevel); } if ( OldZoomLevel != ZoomLevel ) { PostChangedZoom(); // Note: This happens even when maxed out at a stop; so the user sees the animation and knows that they're at max zoom in/out ZoomLevelFade.Play( this->AsShared() ); // Re-center the screen so that it feels like zooming around the cursor. { const FVector2D NewViewOffset = PointToMaintainGraphSpace - WidgetSpaceZoomOrigin / GetZoomAmount(); // If we're panning while zooming we need to update the viewoffset start. ViewOffsetStart += (NewViewOffset - ViewOffset); // Update view offset to where ever we scrolled towards. ViewOffset = NewViewOffset; TotalMouseDelta = 0.0f; } } } FSlateRect SDesignSurface::ComputeSensibleBounds() const { // Pad it out in every direction, to roughly account for nodes being of non-zero extent const float Padding = 100.0f; FSlateRect Bounds = ComputeAreaBounds(); Bounds.Left -= Padding; Bounds.Top -= Padding; Bounds.Right -= Padding; Bounds.Bottom -= Padding; return Bounds; } void SDesignSurface::PostChangedZoom() { } bool SDesignSurface::ScrollToLocation(const FGeometry& MyGeometry, FVector2D DesiredCenterPosition, const float InDeltaTime) { const FVector2D HalfOFScreenInGraphSpace = 0.5f * MyGeometry.GetLocalSize() / GetZoomAmount(); FVector2D CurrentPosition = ViewOffset + HalfOFScreenInGraphSpace; FVector2D NewPosition = FMath::Vector2DInterpTo(CurrentPosition, DesiredCenterPosition, InDeltaTime, 10.f); ViewOffset = NewPosition - HalfOFScreenInGraphSpace; // If within 1 pixel of target, stop interpolating return ( ( NewPosition - DesiredCenterPosition ).SizeSquared() < 1.f ); } bool SDesignSurface::ZoomToLocation(const FVector2D& CurrentSizeWithoutZoom, const FVector2D& InDesiredSize, bool bDoneScrolling) { if ( bAllowContinousZoomInterpolation && ZoomLevelGraphFade.IsPlaying() ) { return false; } const int32 DefaultZoomLevel = ZoomLevels->GetDefaultZoomLevel(); const int32 NumZoomLevels = ZoomLevels->GetNumZoomLevels(); int32 DesiredZoom = DefaultZoomLevel; // Find lowest zoom level that will display all nodes for ( int32 Zoom = 0; Zoom < DefaultZoomLevel; ++Zoom ) { const FVector2D SizeWithZoom = (CurrentSizeWithoutZoom - ZoomToFitPadding) / ZoomLevels->GetZoomAmount(Zoom); const FVector2D LeftOverSize = SizeWithZoom - InDesiredSize; if ( ( InDesiredSize.X > SizeWithZoom.X ) || ( InDesiredSize.Y > SizeWithZoom.Y ) ) { // Use the previous zoom level, this one is too tight DesiredZoom = FMath::Max(0, Zoom - 1); break; } } if ( DesiredZoom != ZoomLevel ) { if ( bAllowContinousZoomInterpolation ) { // Animate to it PreviousZoomLevel = ZoomLevel; ZoomLevel = FMath::Clamp(DesiredZoom, 0, NumZoomLevels - 1); ZoomLevelGraphFade.Play(this->AsShared()); return false; } else { // Do it instantly, either first or last if ( DesiredZoom < ZoomLevel ) { // Zooming out; do it instantly ZoomLevel = PreviousZoomLevel = DesiredZoom; ZoomLevelFade.Play(this->AsShared()); } else { // Zooming in; do it last if ( bDoneScrolling ) { ZoomLevel = PreviousZoomLevel = DesiredZoom; ZoomLevelFade.Play(this->AsShared()); } } } PostChangedZoom(); } return true; } void SDesignSurface::ZoomToFit(bool bInstantZoom) { bTeleportInsteadOfScrollingWhenZoomingToFit = bInstantZoom; bDeferredZoomToExtents = true; } FText SDesignSurface::GetZoomText() const { return ZoomLevels->GetZoomText(ZoomLevel); } FSlateColor SDesignSurface::GetZoomTextColorAndOpacity() const { return FLinearColor(1, 1, 1, 1.25f - ZoomLevelFade.GetLerp()); } FSlateRect SDesignSurface::ComputeAreaBounds() const { return FSlateRect(0, 0, 0, 0); } FVector2D SDesignSurface::GetViewOffset() const { return ViewOffset; } FVector2D SDesignSurface::GraphCoordToPanelCoord(const FVector2D& GraphSpaceCoordinate) const { return ( GraphSpaceCoordinate - GetViewOffset() ) * GetZoomAmount(); } FVector2D SDesignSurface::PanelCoordToGraphCoord(const FVector2D& PanelSpaceCoordinate) const { return PanelSpaceCoordinate / GetZoomAmount() + GetViewOffset(); } int32 SDesignSurface::GetGraphRulePeriod() const { return (int32)FAppStyle::GetFloat("Graph.Panel.GridRulePeriod"); } float SDesignSurface::GetGridScaleAmount() const { return 1; } void SDesignSurface::PaintBackgroundAsLines(const FSlateBrush* BackgroundImage, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32& DrawLayerId) const { const bool bAntialias = GetDefault()->bAntiAliasGrid; const int32 RulePeriod = GetGraphRulePeriod(); check(RulePeriod > 0); const FLinearColor RegularColor(FAppStyle::GetColor("Graph.Panel.GridLineColor")); const FLinearColor RuleColor(FAppStyle::GetColor("Graph.Panel.GridRuleColor")); const FLinearColor CenterColor(FAppStyle::GetColor("Graph.Panel.GridCenterColor")); const float GraphSmallestGridSize = 8.0f; const float RawZoomFactor = GetZoomAmount(); const float NominalGridSize = GetSnapGridSize() * GetGridScaleAmount(); float ZoomFactor = RawZoomFactor; float Inflation = 1.0f; while ( ZoomFactor*Inflation*NominalGridSize <= GraphSmallestGridSize ) { Inflation *= 2.0f; } const float GridCellSize = NominalGridSize * ZoomFactor * Inflation; FVector2f LocalGridOrigin = AllottedGeometry.AbsoluteToLocal(GridOrigin); float ImageOffsetX = LocalGridOrigin.X - ((GridCellSize*RulePeriod) * FMath::Max(FMath::CeilToInt(LocalGridOrigin.X / (GridCellSize*RulePeriod)), 0)); float ImageOffsetY = LocalGridOrigin.Y - ((GridCellSize*RulePeriod) * FMath::Max(FMath::CeilToInt(LocalGridOrigin.Y / (GridCellSize*RulePeriod)), 0)); // Fill the background FSlateDrawElement::MakeBox( OutDrawElements, DrawLayerId, AllottedGeometry.ToPaintGeometry(), BackgroundImage ); if (bDrawGridLines) { TArray LinePoints; new (LinePoints)FVector2D(0.0f, 0.0f); new (LinePoints)FVector2D(0.0f, 0.0f); // Horizontal bars for (int32 GridIndex = 0; ImageOffsetY < AllottedGeometry.GetLocalSize().Y; ImageOffsetY += GridCellSize, ++GridIndex) { if (ImageOffsetY >= 0.0f) { const bool bIsRuleLine = (GridIndex % RulePeriod) == 0; const int32 Layer = bIsRuleLine ? (DrawLayerId + 1) : DrawLayerId; const FLinearColor* Color = bIsRuleLine ? &RuleColor : &RegularColor; if (FMath::IsNearlyEqual(LocalGridOrigin.Y, ImageOffsetY, 1.0f)) { Color = &CenterColor; } LinePoints[0] = FVector2D(0.0f, ImageOffsetY); LinePoints[1] = FVector2D(AllottedGeometry.GetLocalSize().X, ImageOffsetY); FSlateDrawElement::MakeLines( OutDrawElements, Layer, AllottedGeometry.ToPaintGeometry(), LinePoints, ESlateDrawEffect::None, *Color, bAntialias); } } // Vertical bars for (int32 GridIndex = 0; ImageOffsetX < AllottedGeometry.GetLocalSize().X; ImageOffsetX += GridCellSize, ++GridIndex) { if (ImageOffsetX >= 0.0f) { const bool bIsRuleLine = (GridIndex % RulePeriod) == 0; const int32 Layer = bIsRuleLine ? (DrawLayerId + 1) : DrawLayerId; const FLinearColor* Color = bIsRuleLine ? &RuleColor : &RegularColor; if (FMath::IsNearlyEqual(LocalGridOrigin.X, ImageOffsetX, 1.0f)) { Color = &CenterColor; } LinePoints[0] = FVector2D(ImageOffsetX, 0.0f); LinePoints[1] = FVector2D(ImageOffsetX, AllottedGeometry.GetLocalSize().Y); FSlateDrawElement::MakeLines( OutDrawElements, Layer, AllottedGeometry.ToPaintGeometry(), LinePoints, ESlateDrawEffect::None, *Color, bAntialias); } } } DrawLayerId += 2; } #undef LOCTEXT_NAMESPACE