// Copyright Epic Games, Inc. All Rights Reserved. #include "Extensions/CanvasSlotExtension.h" #include "Fonts/SlateFontInfo.h" #include "Misc/Paths.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Rendering/DrawElements.h" #include "Components/CanvasPanelSlot.h" #include "Widgets/SCompoundWidget.h" #include "Styling/CoreStyle.h" #include "Fonts/FontMeasure.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SBox.h" #if WITH_EDITOR #include "Styling/AppStyle.h" #endif // WITH_EDITOR #include "Components/CanvasPanel.h" #include "IUMGDesigner.h" #include "ObjectEditorUtils.h" #define LOCTEXT_NAMESPACE "UMG" ///////////////////////////////////////////////////// // SEventShim class SEventShim : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SEventShim) : _Content() , _OnMouseEnter() , _OnMouseLeave() {} /** Slot for this designers content (optional) */ SLATE_DEFAULT_SLOT(FArguments, Content) SLATE_EVENT(FSimpleDelegate, OnMouseEnter) SLATE_EVENT(FSimpleDelegate, OnMouseLeave) SLATE_END_ARGS() void Construct(const FArguments& InArgs) { MouseEnter = InArgs._OnMouseEnter; MouseLeave = InArgs._OnMouseLeave; ChildSlot [ InArgs._Content.Widget ]; } virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override { SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent); MouseEnter.ExecuteIfBound(); } virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override { SCompoundWidget::OnMouseLeave(MouseEvent); MouseLeave.ExecuteIfBound(); } private: FSimpleDelegate MouseEnter; FSimpleDelegate MouseLeave; }; ///////////////////////////////////////////////////// // FCanvasSlotExtension const double SnapDistance = 7.0; static double DistancePointToLine2D(const FVector2D& LinePointA, const FVector2D& LinePointB, const FVector2D& PointC) { FVector2D AB = LinePointB - LinePointA; FVector2D AC = PointC - LinePointA; double Distance = FVector2D::CrossProduct(AB, AC) / FVector2D::Distance(LinePointA, LinePointB); return FMath::Abs(Distance); } FCanvasSlotExtension::FCanvasSlotExtension() : bMovingAnchor(false) , bHoveringAnchor(false) { ExtensionId = FName(TEXT("CanvasSlot")); } bool FCanvasSlotExtension::CanExtendSelection(const TArray< FWidgetReference >& Selection) const { for ( const FWidgetReference& Widget : Selection ) { if ( !Widget.GetTemplate()->Slot || !Widget.GetTemplate()->Slot->IsA(UCanvasPanelSlot::StaticClass()) ) { return false; } } return Selection.Num() == 1; } void FCanvasSlotExtension::ExtendSelection(const TArray< FWidgetReference >& Selection, TArray< TSharedRef >& SurfaceElements) { SelectionCache = Selection; AnchorWidgets.SetNumZeroed((uint8)EAnchorWidget::Count); AnchorWidgets[(uint8)EAnchorWidget::Center] = MakeAnchorWidget(EAnchorWidget::Center, 16, 16); AnchorWidgets[(uint8)EAnchorWidget::Left] = MakeAnchorWidget(EAnchorWidget::Left, 32, 16); AnchorWidgets[(uint8)EAnchorWidget::Right] = MakeAnchorWidget(EAnchorWidget::Right, 32, 16); AnchorWidgets[(uint8)EAnchorWidget::Top] = MakeAnchorWidget(EAnchorWidget::Top, 16, 32); AnchorWidgets[(uint8)EAnchorWidget::Bottom] = MakeAnchorWidget(EAnchorWidget::Bottom, 16, 32); AnchorWidgets[(uint8)EAnchorWidget::TopLeft] = MakeAnchorWidget(EAnchorWidget::TopLeft, 24, 24); AnchorWidgets[(uint8)EAnchorWidget::TopRight] = MakeAnchorWidget(EAnchorWidget::TopRight, 24, 24); AnchorWidgets[(uint8)EAnchorWidget::BottomLeft] = MakeAnchorWidget(EAnchorWidget::BottomLeft, 24, 24); AnchorWidgets[(uint8)EAnchorWidget::BottomRight] = MakeAnchorWidget(EAnchorWidget::BottomRight, 24, 24); TArray AnchorPos; AnchorPos.SetNumZeroed((uint8)EAnchorWidget::Count); AnchorPos[(uint8)EAnchorWidget::Center] = FVector2D(-8, -8); AnchorPos[(uint8)EAnchorWidget::Left] = FVector2D(-32, -8); AnchorPos[(uint8)EAnchorWidget::Right] = FVector2D(0, -8); AnchorPos[(uint8)EAnchorWidget::Top] = FVector2D(-8, -32); AnchorPos[(uint8)EAnchorWidget::Bottom] = FVector2D(-8, 0); AnchorPos[(uint8)EAnchorWidget::TopLeft] = FVector2D(-24, -24); AnchorPos[(uint8)EAnchorWidget::TopRight] = FVector2D(0, -24); AnchorPos[(uint8)EAnchorWidget::BottomLeft] = FVector2D(-24, 0); AnchorPos[(uint8)EAnchorWidget::BottomRight] = FVector2D(0, 0); for ( int32 AnchorIndex = (int32)EAnchorWidget::Count - 1; AnchorIndex >= 0; AnchorIndex-- ) { if ( !AnchorWidgets[AnchorIndex].IsValid() ) { continue; } AnchorWidgets[AnchorIndex]->SlatePrepass(); TAttribute AnchorAlignment = TAttribute::Create(TAttribute::FGetter::CreateSP(SharedThis(this), &FCanvasSlotExtension::GetAnchorAlignment, (EAnchorWidget)AnchorIndex)); SurfaceElements.Add(MakeShareable(new FDesignerSurfaceElement(AnchorWidgets[AnchorIndex].ToSharedRef(), EExtensionLayoutLocation::RelativeFromParent, AnchorPos[AnchorIndex], AnchorAlignment))); } } TSharedRef FCanvasSlotExtension::MakeAnchorWidget(EAnchorWidget AnchorType, float Width, float Height) { return SNew(SBorder) .BorderImage(FAppStyle::Get().GetBrush("NoBrush")) .OnMouseButtonDown(this, &FCanvasSlotExtension::HandleAnchorBeginDrag, AnchorType) .OnMouseButtonUp(this, &FCanvasSlotExtension::HandleAnchorEndDrag, AnchorType) .OnMouseMove(this, &FCanvasSlotExtension::HandleAnchorDragging, AnchorType) .Visibility(this, &FCanvasSlotExtension::GetAnchorVisibility, AnchorType) .Padding(FMargin(0)) [ SNew(SEventShim) .OnMouseEnter(this, &FCanvasSlotExtension::OnMouseEnterAnchor) .OnMouseLeave(this, &FCanvasSlotExtension::OnMouseLeaveAnchor) [ SNew(SBox) .WidthOverride(Width) .HeightOverride(Height) .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) [ SNew(SImage) .Image(this, &FCanvasSlotExtension::GetAnchorBrush, AnchorType) ] ] ]; } void FCanvasSlotExtension::OnMouseEnterAnchor() { bHoveringAnchor = true; } void FCanvasSlotExtension::OnMouseLeaveAnchor() { bHoveringAnchor = false; } const FSlateBrush* FCanvasSlotExtension::GetAnchorBrush(EAnchorWidget AnchorType) const { switch ( AnchorType ) { case EAnchorWidget::Center: return AnchorWidgets[(uint8)EAnchorWidget::Center]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Center.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Center"); case EAnchorWidget::Left: return AnchorWidgets[(uint8)EAnchorWidget::Left]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Left.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Left"); case EAnchorWidget::Right: return AnchorWidgets[(uint8)EAnchorWidget::Right]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Right.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Right"); case EAnchorWidget::Top: return AnchorWidgets[(uint8)EAnchorWidget::Top]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Top.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Top"); case EAnchorWidget::Bottom: return AnchorWidgets[(uint8)EAnchorWidget::Bottom]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Bottom.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.Bottom"); case EAnchorWidget::TopLeft: return AnchorWidgets[(uint8)EAnchorWidget::TopLeft]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.TopLeft.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.TopLeft"); case EAnchorWidget::BottomRight: return AnchorWidgets[(uint8)EAnchorWidget::BottomRight]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.BottomRight.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.BottomRight"); case EAnchorWidget::TopRight: return AnchorWidgets[(uint8)EAnchorWidget::TopRight]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.TopRight.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.TopRight"); case EAnchorWidget::BottomLeft: return AnchorWidgets[(uint8)EAnchorWidget::BottomLeft]->IsHovered() ? FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.BottomLeft.Hovered") : FAppStyle::Get().GetBrush("UMGEditor.AnchorGizmo.BottomLeft"); } return FCoreStyle::Get().GetBrush("Selection"); } EVisibility FCanvasSlotExtension::GetAnchorVisibility(EAnchorWidget AnchorType) const { for ( const FWidgetReference& Selection : SelectionCache ) { UWidget* PreviewWidget = Selection.GetPreview(); if ( PreviewWidget && PreviewWidget->Slot && !PreviewWidget->bHiddenInDesigner ) { if ( UCanvasPanelSlot* PreviewCanvasSlot = Cast(PreviewWidget->Slot) ) { FAnchorData PreviewLayout = PreviewCanvasSlot->GetLayout(); switch ( AnchorType ) { case EAnchorWidget::Center: return PreviewLayout.Anchors.Minimum == PreviewLayout.Anchors.Maximum ? EVisibility::Visible : EVisibility::Collapsed; case EAnchorWidget::Left: case EAnchorWidget::Right: return PreviewLayout.Anchors.Minimum.Y == PreviewLayout.Anchors.Maximum.Y ? EVisibility::Visible : EVisibility::Collapsed; case EAnchorWidget::Top: case EAnchorWidget::Bottom: return PreviewLayout.Anchors.Minimum.X == PreviewLayout.Anchors.Maximum.X ? EVisibility::Visible : EVisibility::Collapsed; } return EVisibility::Visible; } } } return EVisibility::Collapsed; } FVector2D FCanvasSlotExtension::GetAnchorAlignment(EAnchorWidget AnchorType) const { for ( const FWidgetReference& Selection : SelectionCache ) { UWidget* PreviewWidget = Selection.GetPreview(); if ( PreviewWidget && PreviewWidget->Slot ) { if ( UCanvasPanelSlot* PreviewCanvasSlot = Cast(PreviewWidget->Slot) ) { FVector2D Minimum = PreviewCanvasSlot->GetLayout().Anchors.Minimum; FVector2D Maximum = PreviewCanvasSlot->GetLayout().Anchors.Maximum; switch ( AnchorType ) { case EAnchorWidget::Center: case EAnchorWidget::Left: case EAnchorWidget::Top: case EAnchorWidget::TopLeft: return Minimum; case EAnchorWidget::Right: case EAnchorWidget::Bottom: case EAnchorWidget::BottomRight: return Maximum; case EAnchorWidget::TopRight: return FVector2D(Maximum.X, Minimum.Y); case EAnchorWidget::BottomLeft: return FVector2D(Minimum.X, Maximum.Y); } } } } return FVector2D(0, 0); } bool FCanvasSlotExtension::GetCollisionSegmentsForSlot(UCanvasPanel* Canvas, int32 SlotIndex, TArray& Segments) { FGeometry ArrangedGeometry; if ( Canvas->GetGeometryForSlot(SlotIndex, ArrangedGeometry) ) { GetCollisionSegmentsFromGeometry(ArrangedGeometry, Segments); return true; } return false; } bool FCanvasSlotExtension::GetCollisionSegmentsForSlot(UCanvasPanel* Canvas, UCanvasPanelSlot* Slot, TArray& Segments) { FGeometry ArrangedGeometry; if ( Canvas->GetGeometryForSlot(Slot, ArrangedGeometry) ) { GetCollisionSegmentsFromGeometry(ArrangedGeometry, Segments); return true; } return false; } void FCanvasSlotExtension::GetCollisionSegmentsFromGeometry(FGeometry ArrangedGeometry, TArray& Segments) { Segments.SetNumUninitialized(8); // Left Side Segments[0] = FVector2D(ArrangedGeometry.Position); Segments[1] = FVector2D(ArrangedGeometry.Position) + FVector2D(0, ArrangedGeometry.GetLocalSize().Y); // Top Side Segments[2] = FVector2D(ArrangedGeometry.Position); Segments[3] = FVector2D(ArrangedGeometry.Position) + FVector2D(ArrangedGeometry.GetLocalSize().X, 0); // Right Side Segments[4] = FVector2D(ArrangedGeometry.Position) + FVector2D(ArrangedGeometry.GetLocalSize().X, 0); Segments[5] = FVector2D(ArrangedGeometry.Position) + ArrangedGeometry.GetLocalSize(); // Bottom Side Segments[6] = FVector2D(ArrangedGeometry.Position) + FVector2D(0, ArrangedGeometry.GetLocalSize().Y); Segments[7] = FVector2D(ArrangedGeometry.Position) + ArrangedGeometry.GetLocalSize(); } void FCanvasSlotExtension::Paint(const TSet< FWidgetReference >& Selection, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { //PaintCollisionLines(Selection, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId); PaintDragPercentages(Selection, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId); } FReply FCanvasSlotExtension::HandleAnchorBeginDrag(const FGeometry& Geometry, const FPointerEvent& Event, EAnchorWidget AnchorType) { BeginTransaction(LOCTEXT("MoveAnchor", "Move Anchor")); bMovingAnchor = true; MouseDownPosition = Event.GetScreenSpacePosition(); UCanvasPanelSlot* PreviewCanvasSlot = Cast(SelectionCache[0].GetPreview()->Slot); BeginAnchors = PreviewCanvasSlot->GetLayout().Anchors; Designer->PushDesignerMessage(LOCTEXT("CenterAnchorControls", "Hold [Ctrl] to update widget position")); return FReply::Handled().CaptureMouse(AnchorWidgets[(int32)AnchorType].ToSharedRef()); } FReply FCanvasSlotExtension::HandleAnchorEndDrag(const FGeometry& Geometry, const FPointerEvent& Event, EAnchorWidget AnchorType) { EndTransaction(); bMovingAnchor = false; Designer->PopDesignerMessage(); return FReply::Handled().ReleaseMouseCapture(); } void FCanvasSlotExtension::ProximitySnapValue(float SnapFrequency, float SnapProximity, FVector2D::FReal& Value) { double MajorAnchorDiv = Value / SnapFrequency; double SubAnchorLinePos = MajorAnchorDiv - FMath::RoundToDouble(MajorAnchorDiv); if ( FMath::Abs(SubAnchorLinePos) <= SnapProximity ) { Value = FMath::RoundToDouble(MajorAnchorDiv) * SnapFrequency; } } FReply FCanvasSlotExtension::HandleAnchorDragging(const FGeometry& Geometry, const FPointerEvent& Event, EAnchorWidget AnchorType) { if ( bMovingAnchor && !Event.GetCursorDelta().IsZero() ) { float InverseSize = 1.0f / Designer->GetPreviewScale(); if (SelectionCache.Num() >0) { FWidgetReference& Selection = SelectionCache[0]; UWidget* PreviewWidget = Selection.GetPreview(); if ( UCanvasPanel* Canvas = Cast(PreviewWidget->GetParent()) ) { UCanvasPanelSlot* PreviewCanvasSlot = Cast(PreviewWidget->Slot); FGeometry GeometryForSlot; if ( Canvas->GetGeometryForSlot(PreviewCanvasSlot, GeometryForSlot) ) { FGeometry CanvasGeometry = Canvas->GetCanvasWidget()->GetCachedGeometry(); FVector2D StartLocalPosition = CanvasGeometry.AbsoluteToLocal(MouseDownPosition); FVector2D NewLocalPosition = CanvasGeometry.AbsoluteToLocal(Event.GetScreenSpacePosition()); FVector2D LocalPositionDelta = NewLocalPosition - StartLocalPosition; FVector2D AnchorDelta = LocalPositionDelta / CanvasGeometry.GetLocalSize(); const FAnchorData OldLayoutData = PreviewCanvasSlot->GetLayout(); FAnchorData LayoutData = OldLayoutData; switch ( AnchorType ) { case EAnchorWidget::Center: LayoutData.Anchors.Maximum = BeginAnchors.Maximum + AnchorDelta; LayoutData.Anchors.Minimum = BeginAnchors.Minimum + AnchorDelta; LayoutData.Anchors.Minimum.X = FMath::Clamp(LayoutData.Anchors.Minimum.X, 0.0f, 1.0f); LayoutData.Anchors.Maximum.X = FMath::Clamp(LayoutData.Anchors.Maximum.X, 0.0f, 1.0f); LayoutData.Anchors.Minimum.Y = FMath::Clamp(LayoutData.Anchors.Minimum.Y, 0.0f, 1.0f); LayoutData.Anchors.Maximum.Y = FMath::Clamp(LayoutData.Anchors.Maximum.Y, 0.0f, 1.0f); break; } switch ( AnchorType ) { case EAnchorWidget::Left: case EAnchorWidget::TopLeft: case EAnchorWidget::BottomLeft: LayoutData.Anchors.Minimum.X = BeginAnchors.Minimum.X + AnchorDelta.X; LayoutData.Anchors.Minimum.X = FMath::Clamp(LayoutData.Anchors.Minimum.X, 0.0f, LayoutData.Anchors.Maximum.X); break; } switch ( AnchorType ) { case EAnchorWidget::Right: case EAnchorWidget::TopRight: case EAnchorWidget::BottomRight: LayoutData.Anchors.Maximum.X = BeginAnchors.Maximum.X + AnchorDelta.X; LayoutData.Anchors.Maximum.X = FMath::Clamp(LayoutData.Anchors.Maximum.X, LayoutData.Anchors.Minimum.X, 1.0f); break; } switch ( AnchorType ) { case EAnchorWidget::Top: case EAnchorWidget::TopLeft: case EAnchorWidget::TopRight: LayoutData.Anchors.Minimum.Y = BeginAnchors.Minimum.Y + AnchorDelta.Y; LayoutData.Anchors.Minimum.Y = FMath::Clamp(LayoutData.Anchors.Minimum.Y, 0.0f, LayoutData.Anchors.Maximum.Y); break; } switch ( AnchorType ) { case EAnchorWidget::Bottom: case EAnchorWidget::BottomLeft: case EAnchorWidget::BottomRight: LayoutData.Anchors.Maximum.Y = BeginAnchors.Maximum.Y + AnchorDelta.Y; LayoutData.Anchors.Maximum.Y = FMath::Clamp(LayoutData.Anchors.Maximum.Y, LayoutData.Anchors.Minimum.Y, 1.0f); break; } // Major percentage snapping if ( !FSlateApplication::Get().GetModifierKeys().IsShiftDown() ) { const float MajorAnchorLine = 0.1f; const float MajorAnchorLineSnapDistance = 0.1f; if ( LayoutData.Anchors.Minimum.X != OldLayoutData.Anchors.Minimum.X ) { ProximitySnapValue(MajorAnchorLine, MajorAnchorLineSnapDistance, LayoutData.Anchors.Minimum.X); } if ( LayoutData.Anchors.Minimum.Y != OldLayoutData.Anchors.Minimum.Y ) { ProximitySnapValue(MajorAnchorLine, MajorAnchorLineSnapDistance, LayoutData.Anchors.Minimum.Y); } if ( LayoutData.Anchors.Maximum.X != OldLayoutData.Anchors.Maximum.X ) { ProximitySnapValue(MajorAnchorLine, MajorAnchorLineSnapDistance, LayoutData.Anchors.Maximum.X); } if ( LayoutData.Anchors.Maximum.Y != OldLayoutData.Anchors.Maximum.Y ) { ProximitySnapValue(MajorAnchorLine, MajorAnchorLineSnapDistance, LayoutData.Anchors.Maximum.Y); } } // Rebase the layout and restore the old value after calculating the new final layout // result. { PreviewCanvasSlot->SaveBaseLayout(); PreviewCanvasSlot->SetLayout(LayoutData); PreviewCanvasSlot->RebaseLayout(); LayoutData = PreviewCanvasSlot->GetLayout(); PreviewCanvasSlot->SetLayout(OldLayoutData); } // If control is pressed reset all positional offset information if ( FSlateApplication::Get().GetModifierKeys().IsControlDown() ) { FMargin NewOffsets = LayoutData.Offsets; switch ( AnchorType ) { case EAnchorWidget::Center: NewOffsets = FMargin(0, 0, LayoutData.Anchors.IsStretchedHorizontal() ? 0 : LayoutData.Offsets.Right, LayoutData.Anchors.IsStretchedVertical() ? 0 : LayoutData.Offsets.Bottom); break; case EAnchorWidget::Left: NewOffsets.Left = 0; break; case EAnchorWidget::Right: NewOffsets.Right = 0; break; case EAnchorWidget::Top: NewOffsets.Top = 0; break; case EAnchorWidget::TopLeft: NewOffsets.Top = 0; NewOffsets.Left = 0; break; case EAnchorWidget::TopRight: NewOffsets.Top = 0; NewOffsets.Right = 0; break; case EAnchorWidget::Bottom: NewOffsets.Bottom = 0; break; case EAnchorWidget::BottomLeft: NewOffsets.Bottom = 0; NewOffsets.Left = 0; break; case EAnchorWidget::BottomRight: NewOffsets.Bottom = 0; NewOffsets.Right = 0; break; } LayoutData.Offsets = NewOffsets; } UWidget* TemplateWidget = Selection.GetTemplate(); UCanvasPanelSlot* TemplateCanvasSlot = CastChecked(TemplateWidget->Slot); static const FName LayoutDataName(TEXT("LayoutData")); FObjectEditorUtils::SetPropertyValue(PreviewCanvasSlot, LayoutDataName, LayoutData); FObjectEditorUtils::SetPropertyValue(TemplateCanvasSlot, LayoutDataName, LayoutData); } }; return FReply::Handled(); } } return FReply::Unhandled(); } void FCanvasSlotExtension::PaintDragPercentages(const TSet< FWidgetReference >& InSelection, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { // Just show the percentage lines when we're moving the anchor gizmo if ( !(bMovingAnchor || bHoveringAnchor) ) { return; } for ( const FWidgetReference& Selection : SelectionCache ) { if ( UWidget* PreviewWidget = Selection.GetPreview() ) { if ( UCanvasPanel* Canvas = Cast(PreviewWidget->GetParent()) ) { UCanvasPanelSlot* PreviewCanvasSlot = CastChecked(PreviewWidget->Slot); FGeometry CanvasGeometry; Designer->GetWidgetGeometry(Canvas, CanvasGeometry); // Ignore all widget scales and only use the designer scale (text doesn't need the designer scale, however the rendered lines do) const FGeometry IgnoreScale = CanvasGeometry.MakeChild( CanvasGeometry.GetLocalSize(), Inverse(CanvasGeometry.GetAccumulatedLayoutTransform().GetScale()) * Designer->GetPreviewScale() * AllottedGeometry.Scale ); CanvasGeometry = Designer->MakeGeometryWindowLocal(IgnoreScale); FVector2D CanvasSize = CanvasGeometry.GetLocalSize(); const FAnchorData LayoutData = PreviewCanvasSlot->GetLayout(); const FVector2D AnchorMin = LayoutData.Anchors.Minimum; const FVector2D AnchorMax = LayoutData.Anchors.Maximum; auto DrawSegment =[&] (FVector2D Offset, FVector2D Start, FVector2D End, double Value, FVector2D TextTransform, bool InHorizontalLine) { PaintLineWithText( Start + Offset, End + Offset, FText::FromString(FString::Printf(TEXT("%.1f%%"), Value)), TextTransform, InHorizontalLine, CanvasGeometry, MyCullingRect, OutDrawElements, LayerId); }; // Horizontal { auto DrawHorizontalSegment =[&] (FVector2D Offset, FVector2D TextTransform) { DrawSegment(Offset, FVector2D::ZeroVector, FVector2D(AnchorMin.X * CanvasSize.X, 0.f), AnchorMin.X * 100.f, FVector2D(1.f, TextTransform.Y), true); // Left DrawSegment(Offset, FVector2D(AnchorMax.X * CanvasSize.X, 0.f), FVector2D(CanvasSize.X, 0.f), AnchorMax.X * 100.f, FVector2D(0.f, TextTransform.Y), true); // Right if ( LayoutData.Anchors.IsStretchedHorizontal() ) { DrawSegment(Offset, FVector2D(AnchorMin.X * CanvasSize.X, 0.f), FVector2D(AnchorMax.X * CanvasSize.X, 0.f), ( AnchorMax.X - AnchorMin.X ) * 100.f, FVector2D(0.5f, TextTransform.Y), true); // Center } }; DrawHorizontalSegment(FVector2D(0.f, AnchorMin.Y * CanvasSize.Y), FVector2D(0.f, -1.f)); // Top if ( LayoutData.Anchors.IsStretchedVertical() ) { DrawHorizontalSegment(FVector2D(0.f, AnchorMax.Y * CanvasSize.Y), FVector2D::ZeroVector); // Bottom } } // Vertical { auto DrawVerticalSegment =[&] (FVector2D Offset, FVector2D TextTransform) { DrawSegment(Offset, FVector2D::ZeroVector, FVector2D(0, AnchorMin.Y * CanvasSize.Y), AnchorMin.Y * 100.f, FVector2D(TextTransform.X, 1.f), false); // Top DrawSegment(Offset, FVector2D(0.f, AnchorMax.Y * CanvasSize.Y), FVector2D(0, CanvasSize.Y), AnchorMax.Y * 100.f, FVector2D(TextTransform.X, 0.f), false); // Bottom if ( LayoutData.Anchors.IsStretchedVertical() ) { DrawSegment(Offset, FVector2D(0.f, AnchorMin.Y * CanvasSize.Y), FVector2D(0.f, AnchorMax.Y * CanvasSize.Y), (AnchorMax.Y - AnchorMin.Y) * 100.f, FVector2D(TextTransform.X, 0.5f), false); // Center } }; DrawVerticalSegment(FVector2D(AnchorMin.X * CanvasSize.X, 0.f), FVector2D(-1.f, 0.f)); // Left if ( LayoutData.Anchors.IsStretchedHorizontal() ) { DrawVerticalSegment(FVector2D(AnchorMax.X * CanvasSize.X, 0.f), FVector2D::ZeroVector); // Right } } } } } } void FCanvasSlotExtension::PaintLineWithText(FVector2D Start, FVector2D End, FText Text, FVector2D TextTransform, bool InHorizontalLine, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { TArray LinePoints; LinePoints.AddUninitialized(2); LinePoints[0] = Start; LinePoints[1] = End; FLinearColor Color(0.5f, 0.75f, 1); const bool bAntialias = true; FSlateDrawElement::MakeLines( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), LinePoints, ESlateDrawEffect::None, Color, bAntialias); const float InverseDesignerScale = (1.f / Designer->GetPreviewScale()); const FSlateFontInfo AnchorFont = FCoreStyle::GetDefaultFontStyle("Regular", 10); const FVector2D TextSize = FSlateApplication::Get().GetRenderer()->GetFontMeasureService()->Measure(Text, AnchorFont); FVector2D Offset = FVector2D::ZeroVector; if (InHorizontalLine) { // If the lines run horizontally due to the Y's being the same: Offset.X += ((End - Start).X - TextSize.X) * TextTransform.X; // Plus a little padding (2). Offset.Y += (TextSize.Y * TextTransform.Y) + (20.f * (TextTransform.Y >= 0.f ? 1.f : -1.f)); } else { // If the lines are running vertically: // Plus a little padding (2). Offset.X += (TextSize.X * TextTransform.X) + (20.f * (TextTransform.X >= 0.f ? 1.f : -1.f)); Offset.Y += ((End - Start).Y - TextSize.Y) * TextTransform.Y; } const FGeometry ChildGeometry = AllottedGeometry.MakeChild(AllottedGeometry.GetLocalSize(), FSlateLayoutTransform(Start + Offset)); // Draw drop shadow FSlateDrawElement::MakeText( OutDrawElements, LayerId, ChildGeometry.ToPaintGeometry(TextSize, FSlateLayoutTransform(InverseDesignerScale, FVector2f(1.f, 1.f))), Text, AnchorFont, ESlateDrawEffect::None, FLinearColor::Black ); // Draw normal text on top of drop shadow FSlateDrawElement::MakeText( OutDrawElements, ++LayerId, ChildGeometry.ToPaintGeometry(TextSize, FSlateLayoutTransform(InverseDesignerScale)), Text, AnchorFont, ESlateDrawEffect::None, FLinearColor::White ); } void FCanvasSlotExtension::PaintCollisionLines(const TSet< FWidgetReference >& Selection, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { for ( const FWidgetReference& WidgetRef : Selection ) { if ( !WidgetRef.IsValid() ) { continue; } UWidget* Widget = WidgetRef.GetPreview(); if ( UCanvasPanelSlot* CanvasSlot = Cast(Widget->Slot) ) { if ( UCanvasPanel* Canvas = CastChecked(CanvasSlot->Parent) ) { //TODO UMG Only show guide lines when near them and dragging if ( false ) { FGeometry MyArrangedGeometry; if ( !Canvas->GetGeometryForSlot(CanvasSlot, MyArrangedGeometry) ) { continue; } TArray LinePoints; LinePoints.AddUninitialized(2); // Get the collision segments that we could potentially be docking against. TArray MySegments; if ( GetCollisionSegmentsForSlot(Canvas, CanvasSlot, MySegments) ) { for ( int32 MySegmentIndex = 0; MySegmentIndex < MySegments.Num(); MySegmentIndex += 2 ) { FVector2D MySegmentBase = MySegments[MySegmentIndex]; for ( int32 SlotIndex = 0; SlotIndex < Canvas->GetChildrenCount(); SlotIndex++ ) { // Ignore the slot being dragged. if ( Canvas->GetSlots()[SlotIndex] == CanvasSlot ) { continue; } // Get the collision segments that we could potentially be docking against. TArray Segments; if ( GetCollisionSegmentsForSlot(Canvas, SlotIndex, Segments) ) { for ( int32 SegmentBase = 0; SegmentBase < Segments.Num(); SegmentBase += 2 ) { FVector2D PointA = Segments[SegmentBase]; FVector2D PointB = Segments[SegmentBase + 1]; FVector2D CollisionPoint = MySegmentBase; //TODO Collide against all sides of the arranged geometry. double Distance = DistancePointToLine2D(PointA, PointB, CollisionPoint); if ( Distance <= SnapDistance ) { FVector2D FarthestPoint = FVector2D::Distance(PointA, CollisionPoint) > FVector2D::Distance(PointB, CollisionPoint) ? PointA : PointB; FVector2D NearestPoint = FVector2D::Distance(PointA, CollisionPoint) > FVector2D::Distance(PointB, CollisionPoint) ? PointB : PointA; LinePoints[0] = FarthestPoint; LinePoints[1] = FarthestPoint + ( NearestPoint - FarthestPoint ) * 100000; LinePoints[0].X = FMath::Clamp(LinePoints[0].X, 0.0f, MyCullingRect.Right - MyCullingRect.Left); LinePoints[0].Y = FMath::Clamp(LinePoints[0].Y, 0.0f, MyCullingRect.Bottom - MyCullingRect.Top); LinePoints[1].X = FMath::Clamp(LinePoints[1].X, 0.0f, MyCullingRect.Right - MyCullingRect.Left); LinePoints[1].Y = FMath::Clamp(LinePoints[1].Y, 0.0f, MyCullingRect.Bottom - MyCullingRect.Top); FLinearColor Color(0.5f, 0.75, 1); const bool bAntialias = true; FSlateDrawElement::MakeLines( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), LinePoints, ESlateDrawEffect::None, Color, bAntialias); } } } } } } } } } } } #undef LOCTEXT_NAMESPACE