Files
2025-05-18 13:04:45 +08:00

4005 lines
128 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Designer/SDesignerView.h"
#include "Rendering/DrawElements.h"
#include "Components/PanelWidget.h"
#include "Misc/ConfigCacheIni.h"
#include "WidgetBlueprint.h"
#include "FastUpdate/SlateInvalidationWidgetSortOrder.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/SCanvas.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SGridPanel.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SComboButton.h"
#include "Animation/WidgetAnimation.h"
#include "Components/CanvasPanelSlot.h"
#include "Blueprint/WidgetTree.h"
#include "Settings/WidgetDesignerSettings.h"
#include "ISettingsModule.h"
#include "Designer/DesignTimeUtils.h"
#include "Extensions/CanvasSlotExtension.h"
#include "Extensions/GridSlotExtension.h"
#include "Extensions/HorizontalSlotExtension.h"
#include "Extensions/StackBoxSlotExtension.h"
#include "Extensions/UniformGridSlotExtension.h"
#include "Extensions/VerticalSlotExtension.h"
#include "Designer/SPaintSurface.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "DragAndDrop/DecoratedDragDropOp.h"
#include "DragDrop/WidgetTemplateDragDropOp.h"
#include "DragDrop/SelectedWidgetDragDropOp.h"
#include "Templates/WidgetTemplateBlueprintClass.h"
#include "Templates/WidgetTemplateImageClass.h"
#include "Designer/SZoomPan.h"
#include "Designer/SRuler.h"
#include "Designer/SDisappearingBar.h"
#include "Designer/SDesignerToolBar.h"
#include "Designer/DesignerCommands.h"
#include "Designer/STransformHandle.h"
#include "Engine/UserInterfaceSettings.h"
#include "Widgets/Layout/SDPIScaler.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Engine/Texture2D.h"
#include "Editor.h"
#include "WidgetBlueprintEditorUtils.h"
#include "ObjectEditorUtils.h"
#include "PreviewScene.h"
#include "ScopedTransaction.h"
#include "Components/NamedSlot.h"
#include "Types/ReflectionMetadata.h"
#include "Math/TransformCalculus2D.h"
#include "Input/HittestGrid.h"
#include "Fonts/FontMeasure.h"
#include "WidgetEditingProjectSettings.h"
#include "DeviceProfiles/DeviceProfile.h"
#include "DeviceProfiles/DeviceProfileManager.h"
#include "Engine/DPICustomScalingRule.h"
#include "UMGEditorModule.h"
#include "ToolMenus.h"
#include "Styling/ToolBarStyle.h"
#include "UMGEditorProjectSettings.h"
#include "Types/InvisibleToWidgetReflectorMetaData.h"
#define LOCTEXT_NAMESPACE "UMG"
const float HoveredAnimationTime = 0.150f;
class SResizeDesignerHandle : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SResizeDesignerHandle) {
_Visibility = EVisibility::Visible;
_Cursor = EMouseCursor::ResizeSouthEast;
}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, TSharedPtr<SDesignerView> InDesigner)
{
Designer = InDesigner;
bResizing = false;
ChildSlot
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("UMGEditor.ResizeAreaHandle"))
];
}
// SWidget interface
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override
{
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
bResizing = true;
AbsoluteOffset = MouseEvent.GetScreenSpacePosition() - FVector2D(MyGeometry.AbsolutePosition);
return FReply::Handled().CaptureMouse(SharedThis(this));
}
return FReply::Unhandled();
}
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override
{
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
TSharedPtr<SDesignerView> DesignerView = Designer.Pin();
bResizing = false;
DesignerView->EndResizingArea();
return FReply::Handled().ReleaseMouseCapture();
}
return FReply::Unhandled();
}
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override
{
if (bResizing)
{
TSharedPtr<SDesignerView> DesignerView = Designer.Pin();
if (!DesignerView)
{
return FReply::Unhandled();
}
DesignerView->BeginResizingArea();
const float ZoomAmount = DesignerView->GetZoomAmount();
FVector2D AreaSize = (MouseEvent.GetScreenSpacePosition() - AbsoluteOffset) - DesignerView->GetWidgetOriginAbsolute();
AreaSize /= ZoomAmount;
AreaSize /= MyGeometry.Scale;
if (const UWidgetEditingProjectSettings* Settings = DesignerView->GetRelevantSettings())
{
for (const FDebugResolution& Resolution : Settings->DebugResolutions)
{
if (((AreaSize - FVector2D(Resolution.Width, Resolution.Height)) * ZoomAmount).Size() < 10.0f)
{
AreaSize = FVector2D(Resolution.Width, Resolution.Height);
break;
}
}
}
DesignerView->SetPreviewAreaSize((int32)AreaSize.X, (int32)AreaSize.Y);
return FReply::Handled();
}
return FReply::Unhandled();
}
// End SWidget interface
private:
bool bResizing;
TWeakPtr<SDesignerView> Designer;
FVector2D AbsoluteOffset;
};
struct FWidgetHitResult
{
public:
FWidgetReference Widget;
FArrangedWidget WidgetArranged;
UNamedSlot* NamedSlot;
FArrangedWidget NamedSlotArranged;
public:
FWidgetHitResult()
: WidgetArranged(SNullWidget::NullWidget, FGeometry())
, NamedSlotArranged(SNullWidget::NullWidget, FGeometry())
{
}
};
//////////////////////////////////////////////////////////////////////////
UWidget* SDesignerView::GetWidgetInDesignScopeFromSlateWidget(TSharedRef<SWidget>& InWidget)
{
TSharedPtr<FReflectionMetaData> ReflectionMetadata = InWidget->GetMetaData<FReflectionMetaData>();
if ( ReflectionMetadata.IsValid() )
{
if ( UObject* SourceWidget = ReflectionMetadata->SourceObject.Get() )
{
// The first UUserWidget outer of the source widget should be equal to the PreviewWidget for
// it to be part of the scope of the design area we're dealing with.
if ( SourceWidget->GetTypedOuter<UUserWidget>() == PreviewWidget )
{
return Cast<UWidget>(SourceWidget);
}
}
}
return nullptr;
}
/////////////////////////////////////////////////////
// SDesignerView
const FString SDesignerView::ConfigSectionName = "UMGEditor.Designer";
const FString SDesignerView::DefaultPreviewOverrideName = "";
void SDesignerView::Construct(const FArguments& InArgs, TSharedPtr<FWidgetBlueprintEditor> InBlueprintEditor)
{
ScopedTransaction = nullptr;
PreviewWidget = nullptr;
BlueprintEditor = InBlueprintEditor;
TransformMode = ETransformMode::Layout;
bShowResolutionOutlines = false;
HeightReadFromSettings = 0;
WidthReadFromSettings = 0;
SetStartupResolution();
CachedPreviewDesiredSize = FVector2D(0, 0);
ResolutionTextFade = FCurveSequence(0.0f, 1.0f);
ResolutionTextFade.Play(this->AsShared());
HoveredWidgetOutlineFade = FCurveSequence(0.0f, 0.15f);
SelectedWidgetContextMenuLocation = FVector2D(0, 0);
bMovingExistingWidget = false;
RegisterExtensions();
GEditor->OnBlueprintReinstanced().AddRaw(this, &SDesignerView::OnPreviewNeedsRecreation);
BindCommands();
SDesignSurface::Construct(SDesignSurface::FArguments()
.AllowContinousZoomInterpolation(false)
.Content()
[
SNew(SGridPanel)
.FillColumn(1, 1.0f)
.FillRow(1, 1.0f)
// Corner
+ SGridPanel::Slot(0, 0)
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("GenericWhiteBox"))
.BorderBackgroundColor(FLinearColor(FColor(48, 48, 48)))
]
// Top Ruler
+ SGridPanel::Slot(1, 0)
[
SAssignNew(TopRuler, SRuler)
.Orientation(Orient_Horizontal)
.Visibility(this, &SDesignerView::GetRulerVisibility)
]
// Side Ruler
+ SGridPanel::Slot(0, 1)
[
SAssignNew(SideRuler, SRuler)
.Orientation(Orient_Vertical)
.Visibility(this, &SDesignerView::GetRulerVisibility)
]
// Designer content area
+ SGridPanel::Slot(1, 1)
[
SAssignNew(PreviewHitTestRoot, SOverlay)
.Visibility(EVisibility::Visible)
.Clipping(EWidgetClipping::ClipToBoundsAlways)
// The bottom layer of the overlay where the actual preview widget appears.
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SZoomPan)
.Visibility(EVisibility::HitTestInvisible)
.ZoomAmount(this, &SDesignerView::GetZoomAmount)
.ViewOffset(this, &SDesignerView::GetViewOffset)
[
SNew(SOverlay)
+ SOverlay::Slot()
[
SNew(SBorder)
.Padding(FMargin(0))
.BorderImage(this, &SDesignerView::GetPreviewBackground)
[
SAssignNew(PreviewAreaConstraint, SBox)
.WidthOverride(this, &SDesignerView::GetPreviewAreaWidth)
.HeightOverride(this, &SDesignerView::GetPreviewAreaHeight)
[
SAssignNew(PreviewSurface, SDPIScaler)
.DPIScale(this, &SDesignerView::GetPreviewDPIScale)
[
SAssignNew(PreviewSizeConstraint, SBox)
.WidthOverride(this, &SDesignerView::GetPreviewSizeWidth)
.HeightOverride(this, &SDesignerView::GetPreviewSizeHeight)
]
]
]
]
]
]
// A layer in the overlay where we draw effects, like the highlight effects.
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SAssignNew(EffectsLayer, SPaintSurface)
.OnPaintHandler(this, &SDesignerView::HandleEffectsPainting)
.AddMetaData<FInvisibleToWidgetReflectorMetaData>(FInvisibleToWidgetReflectorMetaData())
]
//
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SAssignNew(DesignerWidgetCanvas, SCanvas)
.Visibility(EVisibility::SelfHitTestInvisible)
+ SCanvas::Slot()
.Size(FVector2D(20, 20))
.Position(TAttribute<FVector2D>::Create(TAttribute<FVector2D>::FGetter::CreateSP(this, &SDesignerView::GetAreaResizeHandlePosition)))
[
SNew(SResizeDesignerHandle, SharedThis(this))
.Visibility(this, &SDesignerView::GetAreaResizeHandleVisibility)
]
]
// A layer in the overlay where we put all the user intractable widgets, like the reorder widgets.
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SAssignNew(ExtensionWidgetCanvas, SCanvas)
.Visibility(this, &SDesignerView::GetExtensionCanvasVisibility)
]
// Designer overlay UI, toolbar, status messages, zoom level...etc
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
CreateOverlayUI()
]
]
]
);
InBlueprintEditor->OnSelectedWidgetsChanged.AddSP(this, &SDesignerView::OnEditorSelectionChanged);
InBlueprintEditor->OnHoveredWidgetSet.AddSP(this, &SDesignerView::OnHoveredWidgetSet);
InBlueprintEditor->OnHoveredWidgetCleared.AddSP(this, &SDesignerView::OnHoveredWidgetCleared);
InBlueprintEditor->OnWidgetPreviewUpdated.AddSP(this, &SDesignerView::OnPreviewNeedsRecreation);
InBlueprintEditor->OnSelectedAnimationChanged.AddSP(this, &SDesignerView::OnSelectedAnimationChanged);
if (const TSharedPtr<ISequencer>& Sequencer = InBlueprintEditor->GetSequencer())
{
Sequencer->OnViewportSelectionLimitedChanged().AddSP(this, &SDesignerView::OnSelectionLimitedChanged);
}
DesignerHittestGrid = MakeShared<FHittestGrid>();
ZoomToFit(/*bInstantZoom*/ true);
FCoreDelegates::OnSafeFrameChangedEvent.AddSP(this, &SDesignerView::SwapSafeZoneTypes);
//RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SDesignerView::EnsureTick));
}
EVisibility SDesignerView::GetExtensionCanvasVisibility() const
{
// If any selected widgets are hidden, then don't show widget extensions.
// If we want to support extensions on mixed-visibility in the future,
// every existing widget extension will probably need to be updated, as
// most do not check widget visibility before performing their function.
for (const FWidgetReference& Widget : GetSelectedWidgets())
{
UWidget* Preview = Widget.GetPreview();
if (!Preview || !Preview->IsVisibleInDesigner())
{
return EVisibility::Hidden;
}
}
return EVisibility::SelfHitTestInvisible;
}
EActiveTimerReturnType SDesignerView::EnsureTick(double InCurrentTime, float InDeltaTime)
{
return EActiveTimerReturnType::Continue;
}
TSharedRef<SWidget> SDesignerView::CreateOverlayUI()
{
const FToolBarStyle& ToolBarStyle = FAppStyle::Get().GetWidgetStyle<FToolBarStyle>("EditorViewportToolBar");
return SNew(SOverlay)
// Outline and text for important state.
+ SOverlay::Slot()
.Padding(0.0f)
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
[
SNew(SOverlay)
.Visibility(this, &SDesignerView::GetDesignerOutlineVisibility)
// Top-right corner text indicating PIE is active
+ SOverlay::Slot()
.Padding(0.0f)
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
[
SNew(SImage)
.ColorAndOpacity(this, &SDesignerView::GetDesignerOutlineColor)
.Image(FAppStyle::GetBrush(TEXT("UMGEditor.DesignerMessageBorder")))
]
// Top-right corner text indicating PIE is active
+ SOverlay::Slot()
.Padding(20.0f)
.VAlign(VAlign_Top)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "Graph.SimulatingText")
.ColorAndOpacity(this, &SDesignerView::GetDesignerOutlineColor)
.Text(this, &SDesignerView::GetDesignerOutlineText)
]
]
// Top bar with buttons for changing the designer
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(6.0f, 2.0f, 0.0f, 0.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), TEXT("Graph.ZoomText"))
.Text(this, &SDesignerView::GetZoomText)
.ColorAndOpacity(this, &SDesignerView::GetZoomTextColorAndOpacity)
.Visibility(EVisibility::SelfHitTestInvisible)
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), TEXT("Graph.ZoomText"))
.Text(LOCTEXT("SequencerSelectionLimited", "Sequencer Selection Limited"))
.ColorAndOpacity(FLinearColor(1.f, 1.f, 1.f, 0.25f))
.Visibility(this, &SDesignerView::GetSelectionLimitedTextVisibility)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(40.0f, 2.0f, 0.0f, 0.0f)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "Graph.ZoomText")
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("BoldCondensed"), 14))
.Text(this, &SDesignerView::GetCursorPositionText)
.ColorAndOpacity(FLinearColor(1.f, 1.f, 1.f, 0.25f))
.Visibility(this, &SDesignerView::GetCursorPositionTextVisibility)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(40.0f, 2.0f, 0.0f, 0.0f)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "Graph.ZoomText")
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("BoldCondensed"), 14))
.Text(this, &SDesignerView::GetSelectedWidgetDimensionsText)
.ColorAndOpacity(FLinearColor(1.f, 1.f, 1.f, 0.25f))
.Visibility(this, &SDesignerView::GetSelectedWidgetDimensionsVisibility)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Top)
[
SNew(SSpacer)
.Size(FVector2D(1.0f, 1.0f))
]
+ SHorizontalBox::Slot()
.Padding(0.0f, 1.0f)
.AutoWidth()
.VAlign(VAlign_Top)
[
SNew(SDesignerToolBar)
.CommandList(CommandList)
]
+ SHorizontalBox::Slot()
.Padding(0.0f, 1.0f)
.AutoWidth()
.VAlign(VAlign_Top)
[
SNew(SButton)
.ButtonStyle(&ToolBarStyle.ButtonStyle)
.ToolTipText(LOCTEXT("ZoomToFit_ToolTip", "Zoom To Fit"))
.OnClicked(this, &SDesignerView::HandleZoomToFitClicked)
.ContentPadding(ToolBarStyle.ButtonPadding)
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("UMGEditor.ZoomToFit"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Top)
[
SNew(SButton)
.ButtonStyle(&ToolBarStyle.ButtonStyle)
.ToolTipText(LOCTEXT("SwapAspectRatio_ToolTip", "Switch between Landscape and Portrait"))
.OnClicked(this, &SDesignerView::HandleSwapAspectRatioClicked)
.ContentPadding(ToolBarStyle.ButtonPadding)
.IsEnabled(this, &SDesignerView::GetAspectRatioSwitchEnabled)
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(this, &SDesignerView::GetAspectRatioSwitchImage)
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Top)
[
SNew(SButton)
.ButtonStyle(&ToolBarStyle.ButtonStyle)
.ToolTipText(LOCTEXT("Mirror_ToolTip", "Flip the current safe zones"))
.OnClicked(this, &SDesignerView::HandleFlipSafeZonesClicked)
.ContentPadding(ToolBarStyle.ButtonPadding)
.IsEnabled(this, &SDesignerView::GetFlipDeviceEnabled)
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("UMGEditor.Mirror"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
// Preview Screen Size
+ SHorizontalBox::Slot()
.Padding(2.0f,0.0f)
.AutoWidth()
.VAlign(VAlign_Top)
[
SNew(SComboButton)
.ButtonStyle(&ToolBarStyle.ButtonStyle)
.OnGetMenuContent(this, &SDesignerView::GetResolutionsMenu)
.ContentPadding(ToolBarStyle.ButtonPadding)
.ButtonContent()
[
SNew(STextBlock)
.Text(LOCTEXT("ScreenSize", "Screen Size"))
.TextStyle(&ToolBarStyle.LabelStyle)
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
// Screen Fill Size Rule
+ SHorizontalBox::Slot()
.Padding(2.0f, 0.0f)
.AutoWidth()
.VAlign(VAlign_Top)
[
SNew(SComboButton)
.ButtonStyle(&ToolBarStyle.ButtonStyle)
.OnGetMenuContent(this, &SDesignerView::GetScreenSizingFillMenu)
.ContentPadding(ToolBarStyle.ButtonPadding)
.ButtonContent()
[
SNew(STextBlock)
.Text(this, &SDesignerView::GetScreenSizingFillText)
.TextStyle(&ToolBarStyle.LabelStyle)
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(2.0f, 0.0f))
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Visibility(this, &SDesignerView::GetCustomResolutionEntryVisibility)
.Text(LOCTEXT("Width", "Width"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(2.0f, 0.0f))
.VAlign(VAlign_Center)
[
SNew(SNumericEntryBox<int32>)
.AllowSpin(true)
.Delta(1)
.MinSliderValue(1)
.MinValue(0)
.MaxSliderValue(TOptional<int32>(10000))
.Value(this, &SDesignerView::GetCustomResolutionWidth)
.OnValueChanged(this, &SDesignerView::OnCustomResolutionWidthChanged)
.Visibility(this, &SDesignerView::GetCustomResolutionEntryVisibility)
.MinDesiredValueWidth(50.0f)
.ToolTipText(LOCTEXT("CustomSize_WidthTooltip", "1+\tSets the width of the widget in the designer.\n0\tThe width will match the desired width of the widget."))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(2.0f, 0.0f))
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Visibility(this, &SDesignerView::GetCustomResolutionEntryVisibility)
.Text(LOCTEXT("Height", "Height"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(2.0f, 0.0f))
.VAlign(VAlign_Center)
[
SNew(SNumericEntryBox<int32>)
.AllowSpin(true)
.Delta(1)
.MinSliderValue(1)
.MaxSliderValue(TOptional<int32>(10000))
.MinValue(0)
.Value(this, &SDesignerView::GetCustomResolutionHeight)
.OnValueChanged(this, &SDesignerView::OnCustomResolutionHeightChanged)
.Visibility(this, &SDesignerView::GetCustomResolutionEntryVisibility)
.MinDesiredValueWidth(50)
.ToolTipText(LOCTEXT("CustomSize_HeightTooltip", "1+\tSets the height of the widget in the designer.\n0\tThe height will match the desired height of the widget."))
]
]
// Info Bar, displays heads up information about some actions.
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Bottom)
[
SNew(SDisappearingBar)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("WhiteBrush"))
.BorderBackgroundColor(FLinearColor(0.10, 0.10, 0.10, 0.75))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(FMargin(0.0f, 5.0f))
.Visibility(this, &SDesignerView::GetInfoBarVisibility)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "Graph.ZoomText")
.Text(this, &SDesignerView::GetInfoBarText)
]
]
]
// Bottom bar to show current resolution & AR
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Bottom)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(6.0f, 0.0f, 0.0f, 2.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
SNew(STextBlock)
.Visibility(this, &SDesignerView::GetResolutionTextVisibility)
.TextStyle(FAppStyle::Get(), "Graph.ZoomText")
.Text(this, &SDesignerView::GetCurrentScaleFactorText)
.ColorAndOpacity(this, &SDesignerView::GetResolutionTextColorAndOpacity)
]
+ SVerticalBox::Slot()
[
SNew(STextBlock)
.Visibility(this, &SDesignerView::GetResolutionTextVisibility)
.TextStyle(FAppStyle::Get(), "Graph.ZoomText")
.Text(this, &SDesignerView::GetCurrentSafeZoneText)
.ColorAndOpacity(this, &SDesignerView::GetResolutionTextColorAndOpacity)
]
+SVerticalBox::Slot()
[
SNew(STextBlock)
.Visibility(this, &SDesignerView::GetResolutionTextVisibility)
.TextStyle(FAppStyle::Get(), "Graph.ZoomText")
.Text(this, &SDesignerView::GetCurrentResolutionText)
.ColorAndOpacity(this, &SDesignerView::GetResolutionTextColorAndOpacity)
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.HAlign(HAlign_Right)
.Padding(0.0f, 0.0f, 6.0f, 2.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Bottom)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "Graph.ZoomText")
.Text(this, &SDesignerView::GetCurrentDPIScaleText)
.ColorAndOpacity(this, &SDesignerView::GetCurrentDPIScaleColor)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
.VAlign(VAlign_Bottom)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
.ContentPadding(FMargin(3, 1))
.OnClicked(this, &SDesignerView::HandleDPISettingsClicked)
.ToolTipText(LOCTEXT("DPISettingsTooltip", "Configure the UI Scale Curve to control how the UI is scaled on different resolutions."))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("UMGEditor.DPISettings"))
]
]
]
];
}
SDesignerView::~SDesignerView()
{
for (const TSharedRef<FDesignerExtension>& Ext : DesignerExtensions)
{
Ext->Uninitialize();
}
DesignerExtensions.Reset();
UWidgetBlueprint* Blueprint = GetBlueprint();
if ( Blueprint )
{
Blueprint->OnChanged().RemoveAll(this);
Blueprint->OnCompiled().RemoveAll(this);
}
if ( BlueprintEditor.IsValid() )
{
auto PinnedEditor = BlueprintEditor.Pin();
PinnedEditor->OnSelectedWidgetsChanged.RemoveAll(this);
PinnedEditor->OnHoveredWidgetSet.RemoveAll(this);
PinnedEditor->OnHoveredWidgetCleared.RemoveAll(this);
PinnedEditor->OnWidgetPreviewUpdated.RemoveAll(this);
PinnedEditor->OnSelectedAnimationChanged.RemoveAll(this);
if (const TSharedPtr<ISequencer> Sequencer = PinnedEditor->GetSequencer())
{
Sequencer->OnViewportSelectionLimitedChanged().RemoveAll(this);
}
}
if ( GEditor )
{
GEditor->OnBlueprintReinstanced().RemoveAll(this);
}
}
void SDesignerView::BindCommands()
{
CommandList = MakeShareable(new FUICommandList);
const FDesignerCommands& Commands = FDesignerCommands::Get();
CommandList->MapAction(
Commands.LayoutTransform,
FExecuteAction::CreateSP(this, &SDesignerView::SetTransformMode, ETransformMode::Layout),
FCanExecuteAction::CreateSP(this, &SDesignerView::CanSetTransformMode, ETransformMode::Layout),
FIsActionChecked::CreateSP(this, &SDesignerView::IsTransformModeActive, ETransformMode::Layout)
);
CommandList->MapAction(
Commands.RenderTransform,
FExecuteAction::CreateSP(this, &SDesignerView::SetTransformMode, ETransformMode::Render),
FCanExecuteAction::CreateSP(this, &SDesignerView::CanSetTransformMode, ETransformMode::Render),
FIsActionChecked::CreateSP(this, &SDesignerView::IsTransformModeActive, ETransformMode::Render)
);
CommandList->MapAction(
Commands.ToggleOutlines,
FExecuteAction::CreateSP(this, &SDesignerView::ToggleShowingOutlines),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SDesignerView::IsShowingOutlines)
);
CommandList->MapAction(
Commands.ToggleRespectLocks,
FExecuteAction::CreateSP(this, &SDesignerView::ToggleRespectingLocks),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SDesignerView::IsRespectingLocks)
);
}
void SDesignerView::AddReferencedObjects(FReferenceCollector& Collector)
{
if ( PreviewWidget )
{
Collector.AddReferencedObject(PreviewWidget);
}
for (auto& DropPreview : DropPreviews)
{
if (DropPreview.Widget)
{
Collector.AddReferencedObject(DropPreview.Widget);
}
if (DropPreview.Parent)
{
Collector.AddReferencedObject(DropPreview.Parent);
}
}
}
void SDesignerView::SetTransformMode(ETransformMode::Type InTransformMode)
{
if ( !InTransaction() )
{
TransformMode = InTransformMode;
}
}
bool SDesignerView::CanSetTransformMode(ETransformMode::Type InTransformMode) const
{
return true;
}
bool SDesignerView::IsTransformModeActive(ETransformMode::Type InTransformMode) const
{
return TransformMode == InTransformMode;
}
void SDesignerView::ToggleShowingOutlines()
{
TSharedPtr<FWidgetBlueprintEditor> Editor = BlueprintEditor.Pin();
Editor->SetShowDashedOutlines(!Editor->GetShowDashedOutlines());
Editor->InvalidatePreview();
}
bool SDesignerView::IsShowingOutlines() const
{
return BlueprintEditor.Pin()->GetShowDashedOutlines();
}
void SDesignerView::ToggleRespectingLocks()
{
TSharedPtr<FWidgetBlueprintEditor> Editor = BlueprintEditor.Pin();
Editor->SetIsRespectingLocks(!Editor->GetIsRespectingLocks());
}
bool SDesignerView::IsRespectingLocks() const
{
return BlueprintEditor.Pin()->GetIsRespectingLocks();
}
void SDesignerView::SetStartupResolution()
{
// Whether the user selected a common resolution.
if (!GConfig->GetBool(*ConfigSectionName, TEXT("bCommonResolutionSelected"), bCommonResolutionSelected, GEditorPerProjectIni))
{
GConfig->SetBool(*ConfigSectionName, TEXT("bCommonResolutionSelected"), false, GEditorPerProjectIni);
bCommonResolutionSelected = false;
}
// Use user-set resolution
const UWidgetDesignerSettings* DesignerSettings = GetDefault<const UWidgetDesignerSettings>();
const FUintVector2 DefaultPreviewResolution = DesignerSettings->DefaultPreviewResolution;
// Width
if (!GConfig->GetInt(*ConfigSectionName, TEXT("PreviewWidth"), PreviewWidth, GEditorPerProjectIni) || !bCommonResolutionSelected)
{
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewWidth"), DefaultPreviewResolution.X, GEditorPerProjectIni);
PreviewWidth = DefaultPreviewResolution.X;
}
// Initially assign WidthReadFromSettings to PreviewWidth
WidthReadFromSettings = PreviewWidth;
// Height
PreviewOverrideName = DefaultPreviewOverrideName;
if (!GConfig->GetInt(*ConfigSectionName, TEXT("PreviewHeight"), PreviewHeight, GEditorPerProjectIni) || !bCommonResolutionSelected)
{
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewHeight"), DefaultPreviewResolution.Y, GEditorPerProjectIni);
PreviewHeight = DefaultPreviewResolution.Y;
}
// Initially assign HeightReadFromSettings to PreviewHeight
HeightReadFromSettings = PreviewHeight;
// Aspect Ratio
if (!GConfig->GetString(*ConfigSectionName, TEXT("PreviewAspectRatio"), PreviewAspectRatio, GEditorPerProjectIni) || !bCommonResolutionSelected)
{
const int32 GCD = FMath::GreatestCommonDivisor(PreviewWidth, PreviewHeight);
PreviewAspectRatio = FString::Printf(TEXT("%d:%d"), PreviewWidth / GCD, PreviewHeight / GCD);
GConfig->SetString(*ConfigSectionName, TEXT("PreviewAspectRatio"), *PreviewAspectRatio, GEditorPerProjectIni);
}
// Portrait Mode
if (!GConfig->GetBool(*ConfigSectionName, TEXT("bIsInPortraitMode"), bPreviewIsPortrait, GEditorPerProjectIni))
{
GConfig->SetBool(*ConfigSectionName, TEXT("bIsInPortraitMode"), false, GEditorPerProjectIni);
bPreviewIsPortrait = false;
}
// Profile Type
if (!GConfig->GetString(*ConfigSectionName, TEXT("ProfileName"), PreviewOverrideName, GEditorPerProjectIni))
{
GConfig->SetString(*ConfigSectionName, TEXT("ProfileName"), *DefaultPreviewOverrideName, GEditorPerProjectIni);
PreviewOverrideName = DefaultPreviewOverrideName;
}
// Scale factor
if (!GConfig->GetFloat(*ConfigSectionName, TEXT("ScaleFactor"), ScaleFactor, GEditorPerProjectIni))
{
GConfig->SetFloat(*ConfigSectionName, TEXT("ScaleFactor"), 1.0f, GEditorPerProjectIni);
ScaleFactor = 1.0f;
}
if (!GConfig->GetBool(*ConfigSectionName, TEXT("bCanPreviewSwapAspectRatio"), bCanPreviewSwapAspectRatio, GEditorPerProjectIni))
{
GConfig->SetBool(*ConfigSectionName, TEXT("bCanPreviewSwapAspectRatio"), false, GEditorPerProjectIni);
bCanPreviewSwapAspectRatio = false;
}
if (!PreviewOverrideName.IsEmpty())
{
ULevelEditorPlaySettings* PlaySettings = GetMutableDefault<ULevelEditorPlaySettings>();
DesignerSafeZoneOverride = PlaySettings->CalculateCustomUnsafeZones(CustomSafeZoneStarts, CustomSafeZoneDimensions, PreviewOverrideName, FVector2D(PreviewWidth, PreviewHeight));
}
else
{
FSlateApplication::Get().ResetCustomSafeZone();
FSlateApplication::Get().GetSafeZoneSize(DesignerSafeZoneOverride, FVector2D(PreviewWidth, PreviewHeight));
}
FMargin SafeZoneRatio = DesignerSafeZoneOverride;
SafeZoneRatio.Left /= (PreviewWidth / 2.0f);
SafeZoneRatio.Right /= (PreviewWidth / 2.0f);
SafeZoneRatio.Bottom /= (PreviewHeight / 2.0f);
SafeZoneRatio.Top /= (PreviewHeight / 2.0f);
FSlateApplication::Get().OnDebugSafeZoneChanged.Broadcast(SafeZoneRatio, true);
}
float SDesignerView::GetPreviewScale() const
{
return GetZoomAmount() * GetPreviewDPIScale();
}
const TSet<FWidgetReference>& SDesignerView::GetSelectedWidgets() const
{
return BlueprintEditor.Pin()->GetSelectedWidgets();
}
FWidgetReference SDesignerView::GetSelectedWidget() const
{
const TSet<FWidgetReference>& SelectedWidgets = BlueprintEditor.Pin()->GetSelectedWidgets();
// Only return a selected widget when we have only a single item selected.
if ( SelectedWidgets.Num() == 1 )
{
TSet<FWidgetReference>::TConstIterator SetIt(SelectedWidgets);
return *SetIt;
}
return FWidgetReference();
}
ETransformMode::Type SDesignerView::GetTransformMode() const
{
return TransformMode;
}
FOptionalSize SDesignerView::GetPreviewAreaWidth() const
{
TTuple<FVector2D, FVector2D> AreaAndSize = FWidgetBlueprintEditorUtils::GetWidgetPreviewAreaAndSize(GetDefaultWidget(), CachedPreviewDesiredSize, FVector2D(PreviewWidth, PreviewHeight), GetDefaultWidget()->DesignSizeMode, TOptional<FVector2D>());
FVector2D Area = AreaAndSize.Get<0>();
return static_cast<float>(Area.X);
}
FOptionalSize SDesignerView::GetPreviewAreaHeight() const
{
TTuple<FVector2D, FVector2D> AreaAndSize = FWidgetBlueprintEditorUtils::GetWidgetPreviewAreaAndSize(GetDefaultWidget(), CachedPreviewDesiredSize, FVector2D(PreviewWidth, PreviewHeight), GetDefaultWidget()->DesignSizeMode, TOptional<FVector2D>());
FVector2D Area = AreaAndSize.Get<0>();;
return static_cast<float>(Area.Y);
}
FOptionalSize SDesignerView::GetPreviewSizeWidth() const
{
TTuple<FVector2D, FVector2D> AreaAndSize = FWidgetBlueprintEditorUtils::GetWidgetPreviewAreaAndSize(GetDefaultWidget(), CachedPreviewDesiredSize, FVector2D(PreviewWidth, PreviewHeight), GetDefaultWidget()->DesignSizeMode, TOptional<FVector2D>());
FVector2D Size = AreaAndSize.Get<1>();
return static_cast<float>(Size.X);
}
FOptionalSize SDesignerView::GetPreviewSizeHeight() const
{
TTuple<FVector2D, FVector2D> AreaAndSize = FWidgetBlueprintEditorUtils::GetWidgetPreviewAreaAndSize(GetDefaultWidget(), CachedPreviewDesiredSize, FVector2D(PreviewWidth, PreviewHeight), GetDefaultWidget()->DesignSizeMode, TOptional<FVector2D>());
FVector2D Size = AreaAndSize.Get<1>();
return static_cast<float>(Size.Y);
}
void SDesignerView::BeginResizingArea()
{
bDrawGridLines = false;
bShowResolutionOutlines = true;
}
void SDesignerView::EndResizingArea()
{
bDrawGridLines = true;
bShowResolutionOutlines = false;
}
const UWidgetEditingProjectSettings* SDesignerView::GetRelevantSettings() const
{
if (UWidgetBlueprint* WidgetBlueprint = GetBlueprint())
{
return WidgetBlueprint->GetRelevantSettings();
}
// Default to the UMG Editor project settings
return GetDefault<UUMGEditorProjectSettings>();
}
void SDesignerView::SetPreviewAreaSize(int32 Width, int32 Height)
{
if (UUserWidget* DefaultWidget = GetDefaultWidget())
{
Width = FMath::Max(Width, 1);
Height = FMath::Max(Height, 1);
switch (DefaultWidget->DesignSizeMode)
{
case EDesignPreviewSizeMode::Custom:
case EDesignPreviewSizeMode::CustomOnScreen:
{
DefaultWidget->DesignTimeSize = FVector2D(Width, Height);
break;
}
default:
{
int32 GCD = FMath::GreatestCommonDivisor(Width, Height);
PreviewWidth = Width;
PreviewHeight = Height;
PreviewAspectRatio = FString::Printf(TEXT("%d:%d"), Width / GCD, Height / GCD);
const bool bSaveChanges = false;
if (bSaveChanges)
{
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewWidth"), Width, GEditorPerProjectIni);
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewHeight"), Height, GEditorPerProjectIni);
GConfig->SetString(*ConfigSectionName, TEXT("PreviewAspectRatio"), *PreviewAspectRatio, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bIsInPortraitMode"), bPreviewIsPortrait, GEditorPerProjectIni);
GConfig->SetString(*ConfigSectionName, TEXT("ProfileName"), *PreviewOverrideName, GEditorPerProjectIni);
GConfig->SetFloat(*ConfigSectionName, TEXT("ScaleFactor"), ScaleFactor, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bCanPreviewSwapAspectRatio"), bCanPreviewSwapAspectRatio, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bCommonResolutionSelected"), false, GEditorPerProjectIni);
}
break;
}
}
}
BroadcastDesignerChanged();
ResolutionTextFade.Play(this->AsShared());
}
FVector2D SDesignerView::GetAreaResizeHandlePosition() const
{
FGeometry PreviewAreaGeometry = PreviewAreaConstraint->GetTickSpaceGeometry();
FGeometry DesignerOverlayGeometry = DesignerWidgetCanvas->GetTickSpaceGeometry();
FVector2D AbsoluteResizeHandlePosition = PreviewAreaGeometry.LocalToAbsolute(PreviewAreaGeometry.GetLocalSize() + FVector2D(2, 2));
return DesignerOverlayGeometry.AbsoluteToLocal(AbsoluteResizeHandlePosition);
}
EVisibility SDesignerView::GetAreaResizeHandleVisibility() const
{
if (UUserWidget* DefaultWidget = GetDefaultWidget())
{
switch (DefaultWidget->DesignSizeMode)
{
case EDesignPreviewSizeMode::Desired:
return EVisibility::Collapsed;
default:
return EVisibility::Visible;
}
}
return EVisibility::Collapsed;
}
const FSlateBrush* SDesignerView::GetPreviewBackground() const
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
if ( DefaultWidget->PreviewBackground )
{
BackgroundImage.SetResourceObject(DefaultWidget->PreviewBackground);
return &BackgroundImage;
}
}
return nullptr;
}
float SDesignerView::GetPreviewDPIScale() const
{
return FWidgetBlueprintEditorUtils::GetWidgetPreviewDPIScale(GetDefaultWidget(), FVector2D(PreviewWidth,PreviewHeight));
}
FSlateRect SDesignerView::ComputeAreaBounds() const
{
return FSlateRect(0, 0, GetPreviewAreaWidth().Get(), GetPreviewAreaHeight().Get());
}
int32 SDesignerView::GetSnapGridSize() const
{
const UWidgetDesignerSettings* DesignerSettings = GetDefault<UWidgetDesignerSettings>();
return DesignerSettings->GridSnapSize;
}
int32 SDesignerView::GetGraphRulePeriod() const
{
return 10;
}
float SDesignerView::GetGridScaleAmount() const
{
return GetPreviewDPIScale();
}
EVisibility SDesignerView::GetInfoBarVisibility() const
{
if ( DesignerMessageStack.Num() > 0 )
{
return EVisibility::Visible;
}
return EVisibility::Hidden;
}
FText SDesignerView::GetInfoBarText() const
{
if ( DesignerMessageStack.Num() > 0 )
{
return DesignerMessageStack.Top();
}
return FText::GetEmpty();
}
void SDesignerView::PushDesignerMessage(const FText& Message)
{
DesignerMessageStack.Push(Message);
}
void SDesignerView::PopDesignerMessage()
{
if ( DesignerMessageStack.Num() > 0)
{
DesignerMessageStack.Pop();
}
}
void SDesignerView::OnEditorSelectionChanged()
{
TSharedPtr<FWidgetBlueprintEditor> BPEd = BlueprintEditor.Pin();
TSet<FWidgetReference> PendingSelectedWidgets = BPEd->GetSelectedWidgets();
// Notify all widgets that are no longer selected.
for ( FWidgetReference& WidgetRef : SelectedWidgetsCache )
{
if ( WidgetRef.IsValid() && !PendingSelectedWidgets.Contains(WidgetRef) )
{
WidgetRef.GetPreview()->DeselectByDesigner();
}
if (UWidget* WidgetTemplate = WidgetRef.GetTemplate())
{
// Find all named slot host widgets that are hierarchical ancestors of this widget and call deselect on them as well
TArray<FWidgetReference> AncestorSlotHostWidgets;
FWidgetBlueprintEditorUtils::FindAllAncestorNamedSlotHostWidgetsForContent(AncestorSlotHostWidgets, WidgetTemplate, BPEd.ToSharedRef());
for (FWidgetReference SlotHostWidget : AncestorSlotHostWidgets)
{
SlotHostWidget.GetPreview()->DeselectByDesigner();
}
}
}
// Notify all widgets that are now selected.
for ( FWidgetReference& WidgetRef : PendingSelectedWidgets )
{
if ( WidgetRef.IsValid() && !SelectedWidgetsCache.Contains(WidgetRef) )
{
WidgetRef.GetPreview()->SelectByDesigner();
if (UWidget* WidgetTemplate = WidgetRef.GetTemplate())
{
// Find all named slot host widgets that are hierarchical ancestors of this widget and call select on them as well
TArray<FWidgetReference> AncestorSlotHostWidgets;
FWidgetBlueprintEditorUtils::FindAllAncestorNamedSlotHostWidgetsForContent(AncestorSlotHostWidgets, WidgetTemplate, BPEd.ToSharedRef());
for (FWidgetReference SlotHostWidget : AncestorSlotHostWidgets)
{
SlotHostWidget.GetPreview()->SelectByDesigner();
}
}
}
}
SelectedWidgetsCache = PendingSelectedWidgets;
CreateExtensionWidgetsForSelection();
}
void SDesignerView::OnHoveredWidgetSet(const FWidgetReference& InHoveredWidget)
{
HoveredWidgetOutlineFade.Play(this->AsShared());
}
void SDesignerView::OnHoveredWidgetCleared()
{
HoveredWidgetOutlineFade.JumpToEnd();
}
FGeometry SDesignerView::GetDesignerGeometry() const
{
return PreviewHitTestRoot->GetTickSpaceGeometry();
}
FVector2D SDesignerView::GetWidgetOriginAbsolute() const
{
if (PreviewWidget)
{
FGeometry Geometry;
if (GetWidgetGeometry(PreviewWidget, Geometry))
{
return FVector2D(Geometry.AbsolutePosition);
}
}
return FVector2D::ZeroVector;
}
void SDesignerView::MarkDesignModifed(bool bRequiresRecompile)
{
if ( bRequiresRecompile )
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint());
}
else
{
FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint());
}
}
bool SDesignerView::GetWidgetParentGeometry(const FWidgetReference& Widget, FGeometry& Geometry) const
{
if ( UWidget* WidgetPreview = Widget.GetPreview() )
{
if ( UPanelWidget* Parent = WidgetPreview->GetParent() )
{
return GetWidgetGeometry(Parent, Geometry);
}
}
Geometry = GetDesignerGeometry();
return true;
}
bool SDesignerView::GetWidgetGeometry(const FWidgetReference& Widget, FGeometry& Geometry) const
{
if ( const UWidget* WidgetPreview = Widget.GetPreview() )
{
return GetWidgetGeometry(WidgetPreview, Geometry);
}
return false;
}
bool SDesignerView::GetWidgetGeometry(const UWidget* InPreviewWidget, FGeometry& Geometry) const
{
TSharedPtr<SWidget> CachedPreviewWidget = InPreviewWidget->GetCachedWidget();
if ( CachedPreviewWidget.IsValid() )
{
const FArrangedWidget* ArrangedWidget = CachedWidgetGeometry.Find(CachedPreviewWidget.ToSharedRef());
if ( ArrangedWidget )
{
Geometry = ArrangedWidget->Geometry;
return true;
}
}
return false;
}
FGeometry SDesignerView::MakeGeometryWindowLocal(const FGeometry& WidgetGeometry) const
{
FGeometry NewGeometry = WidgetGeometry;
TSharedPtr<SWindow> WidgetWindow = FSlateApplication::Get().FindWidgetWindow(SharedThis(this));
if ( WidgetWindow.IsValid() )
{
TSharedRef<SWindow> CurrentWindowRef = WidgetWindow.ToSharedRef();
NewGeometry.AppendTransform(FSlateLayoutTransform(Inverse(CurrentWindowRef->GetPositionInScreen())));
}
return NewGeometry;
}
void SDesignerView::ClearExtensionWidgets()
{
ExtensionWidgetCanvas->ClearChildren();
}
void SDesignerView::CreateExtensionWidgetsForSelection()
{
// Remove all the current extension widgets
ClearExtensionWidgets();
// Get the selected widgets as an array
TArray<FWidgetReference> Selected = GetSelectedWidgets().Array();
TArray< TSharedRef<FDesignerSurfaceElement> > ExtensionElements;
if ( Selected.Num() > 0 )
{
const float Offset = 10;
// Add transform handles
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::TopLeft), EExtensionLayoutLocation::TopLeft, FVector2D(-Offset, -Offset))));
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::TopCenter), EExtensionLayoutLocation::TopCenter, FVector2D(0, -Offset))));
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::TopRight), EExtensionLayoutLocation::TopRight, FVector2D(Offset, -Offset))));
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::CenterLeft), EExtensionLayoutLocation::CenterLeft, FVector2D(-Offset, 0))));
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::CenterRight), EExtensionLayoutLocation::CenterRight, FVector2D(Offset, 0))));
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::BottomLeft), EExtensionLayoutLocation::BottomLeft, FVector2D(-Offset, Offset))));
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::BottomCenter), EExtensionLayoutLocation::BottomCenter, FVector2D(0, Offset))));
ExtensionElements.Add(MakeShareable(new FDesignerSurfaceElement(SNew(STransformHandle, this, ETransformDirection::BottomRight), EExtensionLayoutLocation::BottomRight, FVector2D(Offset, Offset))));
// Build extension widgets for new selection
for ( TSharedRef<FDesignerExtension>& Ext : DesignerExtensions )
{
if ( Ext->CanExtendSelection(Selected) )
{
Ext->ExtendSelection(Selected, ExtensionElements);
}
}
// Add Widgets to designer surface
for ( TSharedRef<FDesignerSurfaceElement>& ExtElement : ExtensionElements )
{
ExtensionWidgetCanvas->AddSlot()
.Position(TAttribute<FVector2D>::Create(TAttribute<FVector2D>::FGetter::CreateSP(this, &SDesignerView::GetExtensionPosition, ExtElement)))
.Size(TAttribute<FVector2D>::Create(TAttribute<FVector2D>::FGetter::CreateSP(this, &SDesignerView::GetExtensionSize, ExtElement)))
[
ExtElement->GetWidget()
];
}
}
}
FVector2D SDesignerView::GetExtensionPosition(TSharedRef<FDesignerSurfaceElement> ExtensionElement) const
{
FWidgetReference SelectedWidget = GetSelectedWidget();
if ( SelectedWidget.IsValid() )
{
FGeometry SelectedWidgetGeometry;
FGeometry SelectedWidgetParentGeometry;
if ( GetWidgetGeometry(SelectedWidget, SelectedWidgetGeometry) && GetWidgetParentGeometry(SelectedWidget, SelectedWidgetParentGeometry) )
{
const FVector2D ParentPostion_DesignerSpace = FVector2D(SelectedWidgetParentGeometry.AbsolutePosition - GetDesignerGeometry().AbsolutePosition) / GetDesignerGeometry().Scale;
const FVector2D ParentSize = SelectedWidgetParentGeometry.Size * GetPreviewScale();
FVector2D FinalPosition(0, 0);
if (ExtensionElement->GetLocation() == EExtensionLayoutLocation::RelativeFromParent)
{
FinalPosition = GetDesignerGeometry().AbsoluteToLocal(SelectedWidgetParentGeometry.LocalToAbsolute(FVector2D(0, 0)));
FinalPosition += ExtensionElement->GetOffset();
}
else
{
FVector2D WidgetPosition = FVector2D::ZeroVector;
// Get the initial offset based on the location around the selected object.
switch (ExtensionElement->GetLocation())
{
case EExtensionLayoutLocation::TopLeft:
WidgetPosition = FVector2D(0, 0);
break;
case EExtensionLayoutLocation::TopCenter:
WidgetPosition = FVector2D(SelectedWidgetGeometry.GetLocalSize().X * 0.5f, 0);
break;
case EExtensionLayoutLocation::TopRight:
WidgetPosition = FVector2D(SelectedWidgetGeometry.GetLocalSize().X, 0);
break;
case EExtensionLayoutLocation::CenterLeft:
WidgetPosition = FVector2D(0, SelectedWidgetGeometry.GetLocalSize().Y * 0.5f);
break;
case EExtensionLayoutLocation::CenterCenter:
WidgetPosition = FVector2D(SelectedWidgetGeometry.GetLocalSize().X * 0.5f, SelectedWidgetGeometry.GetLocalSize().Y * 0.5f);
break;
case EExtensionLayoutLocation::CenterRight:
WidgetPosition = FVector2D(SelectedWidgetGeometry.GetLocalSize().X, SelectedWidgetGeometry.GetLocalSize().Y * 0.5f);
break;
case EExtensionLayoutLocation::BottomLeft:
WidgetPosition = FVector2D(0, SelectedWidgetGeometry.GetLocalSize().Y);
break;
case EExtensionLayoutLocation::BottomCenter:
WidgetPosition = FVector2D(SelectedWidgetGeometry.GetLocalSize().X * 0.5f, SelectedWidgetGeometry.GetLocalSize().Y);
break;
case EExtensionLayoutLocation::BottomRight:
WidgetPosition = SelectedWidgetGeometry.GetLocalSize();
break;
}
FVector2D SelectedWidgetScale = FVector2D(SelectedWidgetGeometry.GetAccumulatedRenderTransform().GetMatrix().GetScale().GetVector());
FVector2D ApplicationScaledOffset = ExtensionElement->GetOffset() * GetDesignerGeometry().Scale;
FVector2D LocalOffsetFull = ApplicationScaledOffset / SelectedWidgetScale;
FVector2D PositionFullOffset = GetDesignerGeometry().AbsoluteToLocal(SelectedWidgetGeometry.LocalToAbsolute(WidgetPosition + LocalOffsetFull));
FVector2D LocalOffsetHalf = (ApplicationScaledOffset / 2.0f) / SelectedWidgetScale;
FVector2D PositionHalfOffset = GetDesignerGeometry().AbsoluteToLocal(SelectedWidgetGeometry.LocalToAbsolute(WidgetPosition + LocalOffsetHalf));
FVector2D PivotCorrection = PositionHalfOffset - (PositionFullOffset + FVector2D(5.0f, 5.0f));
FinalPosition = PositionFullOffset + PivotCorrection;
}
// Add the alignment offset
FinalPosition += ParentSize * ExtensionElement->GetAlignment();
return FinalPosition;
}
}
return FVector2D(0, 0);
}
FVector2D SDesignerView::GetExtensionSize(TSharedRef<FDesignerSurfaceElement> ExtensionElement) const
{
return ExtensionElement->GetWidget()->GetDesiredSize();
}
void SDesignerView::ClearDropPreviews()
{
UWidgetBlueprint* BP = GetBlueprint();
for (const auto& DropPreview : DropPreviews)
{
if (DropPreview.Parent)
{
DropPreview.Parent->RemoveChild(DropPreview.Widget);
}
BP->WidgetTree->RemoveWidget(DropPreview.Widget);
// Since the widget has been removed from the widget tree, move it into the transient package. Otherwise,
// it will remain outered to the widget tree and end up as a property in the BP class layout as a result.
if (DropPreview.Widget->GetOutermost() != GetTransientPackage())
{
DropPreview.Widget->SetFlags(RF_NoFlags);
DropPreview.Widget->Rename(nullptr, GetTransientPackage());
}
}
DropPreviews.Empty();
}
UWidgetBlueprint* SDesignerView::GetBlueprint() const
{
if ( BlueprintEditor.IsValid() )
{
UBlueprint* BP = BlueprintEditor.Pin()->GetBlueprintObj();
return Cast<UWidgetBlueprint>(BP);
}
return nullptr;
}
void SDesignerView::Register(TSharedRef<FDesignerExtension> Extension)
{
if (!DesignerExtensions.Contains(Extension))
{
Extension->Initialize(this, GetBlueprint());
DesignerExtensions.Add(Extension);
}
}
void SDesignerView::Unregister(TSharedRef<FDesignerExtension> Extension)
{
if (DesignerExtensions.Contains(Extension))
{
DesignerExtensions.RemoveSingle(Extension);
Extension->Uninitialize();
}
}
namespace DesignerView
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void RegisterDeprecatedExtensions(SDesignerView* Self, TSharedPtr<FDesignerExtensibilityManager> DesignerExtensibilityManager)
{
for (const TSharedRef<FDesignerExtension>& Extension : DesignerExtensibilityManager->GetExternalDesignerExtensions())
{
Self->Register(Extension);
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void SDesignerView::RegisterExtensions()
{
Register(MakeShareable(new FVerticalSlotExtension()));
Register(MakeShareable(new FHorizontalSlotExtension()));
Register(MakeShareable(new FStackBoxSlotExtension()));
Register(MakeShareable(new FCanvasSlotExtension()));
Register(MakeShareable(new FUniformGridSlotExtension()));
Register(MakeShareable(new FGridSlotExtension()));
//Register External Extensions
IUMGEditorModule& UMGEditorInterface = FModuleManager::GetModuleChecked<IUMGEditorModule>("UMGEditor");
TSharedPtr<FDesignerExtensibilityManager> DesignerExtensibilityManager = UMGEditorInterface.GetDesignerExtensibilityManager();
DesignerView::RegisterDeprecatedExtensions(this, DesignerExtensibilityManager);
for (const TSharedRef<IDesignerExtensionFactory>& ExtensionFactory : DesignerExtensibilityManager->GetExternalDesignerExtensionFactories())
{
Register(ExtensionFactory->CreateDesignerExtension());
}
}
void SDesignerView::OnPreviewNeedsRecreation()
{
// Because widget blueprints can contain other widget blueprints, the safe thing to do is to have all
// designers jettison their previews on the compilation of any widget blueprint. We do this to prevent
// having slate widgets that still may reference data in their owner UWidget that has been garbage collected.
CachedWidgetGeometry.Reset();
PreviewWidget = nullptr;
PreviewSizeConstraint->SetContent(SNullWidget::NullWidget);
// Notify all designer extensions that the content has changed
for (const TSharedRef<FDesignerExtension>& Ext : DesignerExtensions)
{
Ext->PreviewContentChanged(SNullWidget::NullWidget);
}
}
SDesignerView::FWidgetHitResult::FWidgetHitResult()
: Widget()
, WidgetArranged(SNullWidget::NullWidget, FGeometry())
, NamedSlot(NAME_None)
{
}
bool SDesignerView::FindWidgetUnderCursor(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, TSubclassOf<UWidget> FindType, FWidgetHitResult& HitResult)
{
//@TODO UMG Make it so you can request dropable widgets only, to find the first parentable.
// Query the hit test grid we create for the design surface, and determine what widgets we hit.
TArray<FWidgetAndPointer> BubblePath = DesignerHittestGrid->GetBubblePath(MouseEvent.GetScreenSpacePosition(), 0.0f, true, INDEX_NONE);
HitResult.Widget = FWidgetReference();
HitResult.NamedSlot = NAME_None;
UUserWidget* PreviewUserWidget = BlueprintEditor.Pin()->GetPreview();
if ( PreviewUserWidget )
{
UWidget* WidgetUnderCursor = nullptr;
// We loop through each hit slate widget until we arrive at one that we can access from the root widget.
for ( int32 ChildIndex = BubblePath.Num() - 1; ChildIndex >= 0; ChildIndex-- )
{
FArrangedWidget& Child = BubblePath[ChildIndex];
WidgetUnderCursor = PreviewUserWidget->GetWidgetHandle(Child.Widget);
if (WidgetUnderCursor == nullptr)
{
continue;
}
// Ignore the drop preview widgets when doing widget picking
if (DropPreviews.ContainsByPredicate([WidgetUnderCursor](const FDropPreview& Preview){ return Preview.Widget == WidgetUnderCursor; }))
{
WidgetUnderCursor = nullptr;
continue;
}
// Ignore widgets that don't pass our find widget filter.
if ( WidgetUnderCursor->GetClass()->IsChildOf(FindType) == false )
{
WidgetUnderCursor = nullptr;
continue;
}
// We successfully found a widget that's accessible from the root.
if ( WidgetUnderCursor )
{
HitResult.Widget = BlueprintEditor.Pin()->GetReferenceFromPreview(WidgetUnderCursor);
HitResult.WidgetArranged = Child;
if ( UUserWidget* UserWidgetUnderCursor = Cast<UUserWidget>(WidgetUnderCursor) )
{
// Find the named slot we're over, if any
for ( int32 SubChildIndex = BubblePath.Num() - 1; SubChildIndex > ChildIndex; SubChildIndex-- )
{
FArrangedWidget& SubChild = BubblePath[SubChildIndex];
UNamedSlot* NamedSlot = Cast<UNamedSlot>(UserWidgetUnderCursor->GetWidgetHandle(SubChild.Widget));
if ( NamedSlot )
{
HitResult.NamedSlot = NamedSlot->GetFName();
break;
}
}
}
return true;
}
}
}
return false;
}
void SDesignerView::ResolvePendingSelectedWidgets()
{
if ( PendingSelectedWidget.IsValid() )
{
TSet<FWidgetReference> SelectedTemplates;
SelectedTemplates.Add(PendingSelectedWidget);
bool AppendOrToggle = FSlateApplication::Get().GetModifierKeys().IsControlDown() || FSlateApplication::Get().GetModifierKeys().IsShiftDown();
BlueprintEditor.Pin()->SelectWidgets(SelectedTemplates, AppendOrToggle);
PendingSelectedWidget = FWidgetReference();
}
}
FReply SDesignerView::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
SDesignSurface::OnMouseButtonDown(MyGeometry, MouseEvent);
//TODO UMG Undoable Selection
bool bFoundWidgetUnderCursor = false;
{
// Narrow life scope of FWidgetHitResult so it doesn't keep a hard reference on any widget.
FWidgetHitResult HitResult;
bFoundWidgetUnderCursor = FindWidgetUnderCursor(MyGeometry, MouseEvent, UWidget::StaticClass(), HitResult);
if (bFoundWidgetUnderCursor)
{
SelectedWidgetContextMenuLocation = HitResult.WidgetArranged.Geometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
PendingSelectedWidget = HitResult.Widget;
}
}
if (bFoundWidgetUnderCursor)
{
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
{
if (IsSelectableInSequencer(PendingSelectedWidget.GetPreview()))
{
const TSet<FWidgetReference>& SelectedWidgets = GetSelectedWidgets();
bool bResolvePendingSelectionImmediately = true;
if (SelectedWidgets.Num() > 0)
{
for (const auto& SelectedWidget : SelectedWidgets)
{
auto PendingTemplate = PendingSelectedWidget.GetTemplate();
auto SelectedTemplate = SelectedWidget.GetTemplate();
if ( PendingSelectedWidget == SelectedWidget || ( PendingTemplate && SelectedTemplate && PendingTemplate->IsChildOf( SelectedTemplate ) ) )
{
bResolvePendingSelectionImmediately = false;
break;
}
}
}
// If the newly clicked item is a child of the active selection, add it to the pending set of selected
// widgets, if they begin dragging we can just move the parent, but if it's not part of the parent set,
// we want to immediately begin dragging it. Also if the currently selected widget is the root widget,
// we won't be moving it so just resolve immediately.
if ( bResolvePendingSelectionImmediately )
{
ResolvePendingSelectedWidgets();
}
DraggingStartPositionScreenSpace = MouseEvent.GetScreenSpacePosition();
}
else
{
PendingSelectedWidget = FWidgetReference();
}
}
}
else
{
// Clear the selection immediately if we didn't click anything.
if(MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
TSet<FWidgetReference> SelectedTemplates;
BlueprintEditor.Pin()->SelectWidgets(SelectedTemplates, false);
}
}
// Capture mouse for the drag handle and general mouse actions
return FReply::Handled().PreventThrottling().SetUserFocus(AsShared(), EFocusCause::Mouse).CaptureMouse(AsShared());
}
FReply SDesignerView::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if ( HasMouseCapture() && MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
{
ResolvePendingSelectedWidgets();
bMovingExistingWidget = false;
EndTransaction(false);
}
else if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton )
{
if ( !bIsPanning && !bIsZooming )
{
ResolvePendingSelectedWidgets();
ShowContextMenu(MyGeometry, MouseEvent);
}
}
SDesignSurface::OnMouseButtonUp(MyGeometry, MouseEvent);
return FReply::Handled().ReleaseMouseCapture();
}
FReply SDesignerView::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if ( MouseEvent.GetCursorDelta().IsZero() )
{
return FReply::Unhandled();
}
CachedMousePosition = MouseEvent.GetScreenSpacePosition();
FReply SurfaceHandled = SDesignSurface::OnMouseMove(MyGeometry, MouseEvent);
if ( SurfaceHandled.IsEventHandled() )
{
return SurfaceHandled;
}
if ( MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && HasMouseCapture() )
{
const TSet<FWidgetReference>& SelectedWidgets = GetSelectedWidgets();
if (SelectedWidgets.Num() > 0 && !bMovingExistingWidget)
{
if ( TransformMode == ETransformMode::Layout )
{
bool bIsRootWidgetSelected = false;
for (const auto& SelectedWidget : SelectedWidgets)
{
UWidget* ParentWidget = SelectedWidget.GetTemplate()->GetParent();
if (!ParentWidget || Cast<UNamedSlot>(ParentWidget))
{
bIsRootWidgetSelected = true;
break;
}
}
if (!bIsRootWidgetSelected)
{
bMovingExistingWidget = true;
//Drag selected widgets
return FReply::Handled().DetectDrag(AsShared(), EKeys::LeftMouseButton);
}
}
else
{
checkSlow(TransformMode == ETransformMode::Render);
checkSlow(bMovingExistingWidget == false);
if (SelectedWidgets.Num() == 1)
{
BeginTransaction(LOCTEXT("MoveWidgetRT", "Move Widget (Render Transform)"));
}
else
{
check(SelectedWidgets.Num() > 1);
BeginTransaction(LOCTEXT("MoveWidgetsRT", "Move Widgets (Render Transform)"));
}
for (const auto& SelectedWidget : SelectedWidgets)
{
if (UWidget* WidgetPreview = SelectedWidget.GetPreview())
{
FGeometry ParentGeometry;
if (GetWidgetParentGeometry(SelectedWidget, ParentGeometry))
{
const FSlateRenderTransform& AbsoluteToLocalTransform = Inverse(ParentGeometry.GetAccumulatedRenderTransform());
FWidgetTransform WidgetRenderTransform = WidgetPreview->GetRenderTransform();
WidgetRenderTransform.Translation += TransformVector(AbsoluteToLocalTransform, MouseEvent.GetCursorDelta());
static const FName RenderTransformName(TEXT("RenderTransform"));
FObjectEditorUtils::SetPropertyValue<UWidget, FWidgetTransform>(WidgetPreview, RenderTransformName, WidgetRenderTransform);
FObjectEditorUtils::SetPropertyValue<UWidget, FWidgetTransform>(SelectedWidget.GetTemplate(), RenderTransformName, WidgetRenderTransform);
}
}
}
}
}
}
// Update the hovered widget under the mouse
if (const TSharedPtr<FWidgetBlueprintEditor> PinnedBlueprintEditor = BlueprintEditor.Pin())
{
FWidgetHitResult HitResult;
if (FindWidgetUnderCursor(MyGeometry, MouseEvent, UWidget::StaticClass(), HitResult)
&& IsSelectableInSequencer(HitResult.Widget.GetPreview()))
{
PinnedBlueprintEditor->SetHoveredWidget(HitResult.Widget);
}
else if (PinnedBlueprintEditor->GetHoveredWidget().IsValid())
{
PinnedBlueprintEditor->ClearHoveredWidget();
}
}
return FReply::Unhandled();
}
void SDesignerView::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
SDesignSurface::OnMouseEnter(MyGeometry, MouseEvent);
BlueprintEditor.Pin()->ClearHoveredWidget();
}
void SDesignerView::OnMouseLeave(const FPointerEvent& MouseEvent)
{
SDesignSurface::OnMouseLeave(MouseEvent);
BlueprintEditor.Pin()->ClearHoveredWidget();
}
FReply SDesignerView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
UWidget* SelectedWidget = GetSelectedWidget().GetPreview();
//If the selected widget is a canvas panel, we'd like to drop the widget right under the cursor
//Otherwise, we'll just cascade it off the current selection
if(SelectedWidget)
{
if (SelectedWidget->IsA(UPanelWidget::StaticClass()))
{
BlueprintEditor.Pin()->PasteDropLocation = SelectedWidget->GetTickSpaceGeometry().AbsoluteToLocal(CachedMousePosition);
}
else
{
UCanvasPanelSlot* WidgetSlot = Cast<UCanvasPanelSlot>(SelectedWidget->Slot);
if (WidgetSlot)
{
BlueprintEditor.Pin()->PasteDropLocation = WidgetSlot->GetPosition() + FVector2D(25, 25);
}
}
}
if ( BlueprintEditor.Pin()->DesignerCommandList->ProcessCommandBindings(InKeyEvent) )
{
return FReply::Handled();
}
if ( CommandList->ProcessCommandBindings(InKeyEvent) )
{
return FReply::Handled();
}
const UWidgetDesignerSettings* DesignerSettings = GetDefault<UWidgetDesignerSettings>();
if ( InKeyEvent.GetKey() == EKeys::Up )
{
return NudgeSelectedWidget(FVector2D(0, -DesignerSettings->GridSnapSize));
}
else if ( InKeyEvent.GetKey() == EKeys::Down )
{
return NudgeSelectedWidget(FVector2D(0, DesignerSettings->GridSnapSize));
}
else if ( InKeyEvent.GetKey() == EKeys::Left )
{
return NudgeSelectedWidget(FVector2D(-DesignerSettings->GridSnapSize, 0));
}
else if ( InKeyEvent.GetKey() == EKeys::Right )
{
return NudgeSelectedWidget(FVector2D(DesignerSettings->GridSnapSize, 0));
}
return FReply::Unhandled();
}
FReply SDesignerView::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
return FReply::Unhandled();
}
FReply SDesignerView::NudgeSelectedWidget(FVector2D Nudge)
{
for ( const FWidgetReference& WidgetRef : GetSelectedWidgets() )
{
if ( WidgetRef.IsValid() )
{
UWidget* CurrentTemplateWidget = WidgetRef.GetTemplate();
UWidget* CurrentPreviewWidget = WidgetRef.GetPreview();
if (CurrentTemplateWidget && CurrentPreviewWidget)
{
UPanelSlot* TemplateSlot = CurrentTemplateWidget->Slot;
UPanelSlot* PreviewSlot = CurrentPreviewWidget->Slot;
if ( TemplateSlot && PreviewSlot )
{
FScopedTransaction Transaction(LOCTEXT("Designer_NudgeWidget", "Nudge Widget"));
const UWidgetDesignerSettings* const WidgetDesignerSettings = GetDefault<UWidgetDesignerSettings>();
// Attempt to nudge the slot.
if (TemplateSlot->NudgeByDesigner(Nudge, WidgetDesignerSettings->GridSnapEnabled ? TOptional<int32>(WidgetDesignerSettings->GridSnapSize) : TOptional<int32>()))
{
PreviewSlot->SynchronizeFromTemplate(TemplateSlot);
UWidgetBlueprint* Blueprint = GetBlueprint();
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
// Nudge failed, cancel transaction.
else
{
Transaction.Cancel();
}
}
}
}
}
return FReply::Handled();
}
void SDesignerView::ShowContextMenu(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FMenuBuilder MenuBuilder(true, nullptr);
FWidgetBlueprintEditorUtils::CreateWidgetContextMenu(MenuBuilder, BlueprintEditor.Pin().ToSharedRef(), SelectedWidgetContextMenuLocation);
TSharedPtr<SWidget> MenuContent = MenuBuilder.MakeWidget();
if ( MenuContent.IsValid() )
{
FVector2D SummonLocation = MouseEvent.GetScreenSpacePosition();
FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().PushMenu(AsShared(), WidgetPath, MenuContent.ToSharedRef(), SummonLocation, FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
}
}
void SDesignerView::PopulateWidgetGeometryCache(FArrangedWidget& Root)
{
const FSlateRect Rect = PreviewHitTestRoot->GetTickSpaceGeometry().GetLayoutBoundingRect();
const FSlateRect PaintRect = PreviewHitTestRoot->GetPaintSpaceGeometry().GetLayoutBoundingRect();
DesignerHittestGrid->SetHittestArea(Rect.GetTopLeft(), Rect.GetSize(), PaintRect.GetTopLeft());
DesignerHittestGrid->Clear();
PopulateWidgetGeometryCache_Loop(Root);
}
void SDesignerView::PopulateWidgetGeometryCache_Loop(FArrangedWidget& CurrentWidget)
{
// If this widget clips to its bounds, then generate a new clipping rect representing the intersection of the bounding
// rectangle of the widget's geometry, and the current clipping rectangle.
bool bClipToBounds, bAlwaysClip, bIntersectClipBounds;
FSlateRect CullingBounds = CurrentWidget.Widget->CalculateCullingAndClippingRules(CurrentWidget.Geometry, FSlateRect(), bClipToBounds, bAlwaysClip, bIntersectClipBounds);
// NOTE: We're unable to deal with custom clipping states with this method, we'd have to do the true paint
// for widgets, which would be much more expensive.
if (bClipToBounds)
{
// The hit test grid records things in desktop space, so we use the tick geometry instead of the paint geometry.
FSlateClippingZone DesktopClippingZone(CurrentWidget.Geometry);
DesktopClippingZone.SetShouldIntersectParent(bIntersectClipBounds);
DesktopClippingZone.SetAlwaysClip(bAlwaysClip);
}
bool bIncludeInHitTestGrid = false;
// Widgets that are children of foreign userWidgets should not be considered selection candidates.
UWidget* CandidateUWidget = GetWidgetInDesignScopeFromSlateWidget(CurrentWidget.Widget);
if (CandidateUWidget)
{
bool bRespectLocks = IsRespectingLocks();
if (!CandidateUWidget->IsVisibleInDesigner())
{
bIncludeInHitTestGrid = false;
}
else if (bRespectLocks && CandidateUWidget->IsLockedInDesigner())
{
bIncludeInHitTestGrid = false;
}
else
{
bIncludeInHitTestGrid = true;
}
}
if (bIncludeInHitTestGrid)
{
DesignerHittestGrid->AddWidget(&(CurrentWidget.Widget.Get()), 0, 0, FSlateInvalidationWidgetSortOrder());
}
FArrangedChildren ArrangedChildren(EVisibility::All);
CurrentWidget.Widget->ArrangeChildren(CurrentWidget.Geometry, ArrangedChildren);
CachedWidgetGeometry.Add(CurrentWidget.Widget, CurrentWidget);
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex)
{
FArrangedWidget& SomeChild = ArrangedChildren[ChildIndex];
PopulateWidgetGeometryCache_Loop(SomeChild);
}
}
int32 SDesignerView::HandleEffectsPainting(const FOnPaintHandlerParams& PaintArgs)
{
DrawSelectionAndHoverOutline(PaintArgs);
DrawSafeZone(PaintArgs);
return PaintArgs.Layer + 1;
}
void SDesignerView::DrawSelectionAndHoverOutline(const FOnPaintHandlerParams& PaintArgs)
{
const TSet<FWidgetReference>& SelectedWidgets = GetSelectedWidgets();
// Allow the extensions to paint anything they want.
for ( const TSharedRef<FDesignerExtension>& Ext : DesignerExtensions )
{
Ext->Paint(SelectedWidgets, PaintArgs.Geometry, PaintArgs.ClippingRect, PaintArgs.OutDrawElements, PaintArgs.Layer);
}
const FLinearColor SelectedTint(0, 1, 0);
const bool bAntiAlias = false;
for ( const FWidgetReference& SelectedWidget : SelectedWidgets )
{
TSharedPtr<SWidget> SelectedSlateWidget = SelectedWidget.GetPreviewSlate();
if ( SelectedSlateWidget.IsValid() )
{
TSharedRef<SWidget> Widget = SelectedSlateWidget.ToSharedRef();
FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry());
FDesignTimeUtils::GetArrangedWidgetRelativeToWindow(Widget, ArrangedWidget);
// Draw selection effect
const FVector2D OutlinePixelSize = FVector2D(2.0f, 2.0f) / FVector2D(ArrangedWidget.Geometry.GetAccumulatedRenderTransform().GetMatrix().GetScale().GetVector());
FPaintGeometry SelectionGeometry = ArrangedWidget.Geometry.ToInflatedPaintGeometry(OutlinePixelSize);
FSlateClippingZone SelectionZone(SelectionGeometry);
TArray<FVector2D> Points;
Points.Add(FVector2D(SelectionZone.TopLeft));
Points.Add(FVector2D(SelectionZone.TopRight));
Points.Add(FVector2D(SelectionZone.BottomRight));
Points.Add(FVector2D(SelectionZone.BottomLeft));
Points.Add(FVector2D(SelectionZone.TopLeft));
FSlateDrawElement::MakeLines(
PaintArgs.OutDrawElements,
PaintArgs.Layer,
FPaintGeometry(),
Points,
ESlateDrawEffect::None,
SelectedTint,
bAntiAlias,
2.0f);
}
}
const FWidgetReference& HoveredWidget = BlueprintEditor.Pin()->GetHoveredWidget();
TSharedPtr<SWidget> HoveredSlateWidget = HoveredWidget.GetPreviewSlate();
// Don't draw the hovered effect if it's also the selected widget
if ( HoveredSlateWidget.IsValid() && !SelectedWidgets.Contains(HoveredWidget) )
{
const FLinearColor HoveredTint(0, 0.5, 1, HoveredWidgetOutlineFade.GetLerp()); // Azure = 0x007FFF
TSharedRef<SWidget> Widget = HoveredSlateWidget.ToSharedRef();
FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry());
FDesignTimeUtils::GetArrangedWidgetRelativeToWindow(Widget, ArrangedWidget);
// Draw hovered effect
const FVector2D OutlinePixelSize = FVector2D(2.0f, 2.0f) / FVector2D(ArrangedWidget.Geometry.GetAccumulatedRenderTransform().GetMatrix().GetScale().GetVector());
FPaintGeometry HoveredGeometry = ArrangedWidget.Geometry.ToInflatedPaintGeometry(OutlinePixelSize);
FSlateClippingZone HoveredZone(HoveredGeometry);
TArray<FVector2D> Points;
Points.Add(FVector2D(HoveredZone.TopLeft));
Points.Add(FVector2D(HoveredZone.TopRight));
Points.Add(FVector2D(HoveredZone.BottomRight));
Points.Add(FVector2D(HoveredZone.BottomLeft));
Points.Add(FVector2D(HoveredZone.TopLeft));
FSlateDrawElement::MakeLines(
PaintArgs.OutDrawElements,
PaintArgs.Layer,
FPaintGeometry(),
Points,
ESlateDrawEffect::None,
HoveredTint,
bAntiAlias,
2.0f);
}
}
void SDesignerView::DrawSafeZone(const FOnPaintHandlerParams& PaintArgs)
{
bool bCanShowSafeZone = false;
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
switch ( DefaultWidget->DesignSizeMode )
{
case EDesignPreviewSizeMode::CustomOnScreen:
case EDesignPreviewSizeMode::DesiredOnScreen:
case EDesignPreviewSizeMode::FillScreen:
bCanShowSafeZone = true;
break;
}
}
if ( bCanShowSafeZone )
{
const float UnsafeZoneAlpha = 0.2f;
const FLinearColor UnsafeZoneColor(1.0f, 0.5f, 0.5f, UnsafeZoneAlpha);
const FSlateBrush* WhiteBrush = FAppStyle::GetBrush("WhiteBrush");
FGeometry PreviewGeometry = PreviewAreaConstraint->GetTickSpaceGeometry();
PreviewGeometry.AppendTransform(FSlateLayoutTransform(Inverse(PaintArgs.Args.GetWindowToDesktopTransform())));
const float Width = static_cast<float>(PreviewWidth);
const float Height = static_cast<float>(PreviewHeight);
if (PreviewOverrideName.IsEmpty())
{
FMargin SafeMargin;
FSlateApplication::Get().ResetCustomSafeZone();
FSlateApplication::Get().GetSafeZoneSize(SafeMargin, FVector2f(Width, Height));
const float HeightOfSides = Height - SafeMargin.GetTotalSpaceAlong<Orient_Vertical>();
// Top bar
FSlateDrawElement::MakeBox(
PaintArgs.OutDrawElements,
PaintArgs.Layer,
PreviewGeometry.ToPaintGeometry(FVector2f(Width, SafeMargin.Top), FSlateLayoutTransform()),
WhiteBrush,
ESlateDrawEffect::None,
UnsafeZoneColor
);
// Bottom bar
FSlateDrawElement::MakeBox(
PaintArgs.OutDrawElements,
PaintArgs.Layer,
PreviewGeometry.ToPaintGeometry(FVector2f(Width, SafeMargin.Bottom), FSlateLayoutTransform(FVector2f(0.0f, Height - SafeMargin.Bottom))),
WhiteBrush,
ESlateDrawEffect::None,
UnsafeZoneColor
);
// Left bar
FSlateDrawElement::MakeBox(
PaintArgs.OutDrawElements,
PaintArgs.Layer,
PreviewGeometry.ToPaintGeometry(FVector2f(SafeMargin.Left, HeightOfSides), FSlateLayoutTransform(FVector2f(0.0f, SafeMargin.Top))),
WhiteBrush,
ESlateDrawEffect::None,
UnsafeZoneColor
);
// Right bar
FSlateDrawElement::MakeBox(
PaintArgs.OutDrawElements,
PaintArgs.Layer,
PreviewGeometry.ToPaintGeometry(FVector2f(SafeMargin.Right, HeightOfSides), FSlateLayoutTransform(FVector2f(Width - SafeMargin.Right, SafeMargin.Top))),
WhiteBrush,
ESlateDrawEffect::None,
UnsafeZoneColor
);
}
else
{
ULevelEditorPlaySettings* PlaySettings = GetMutableDefault<ULevelEditorPlaySettings>();
if (bSafeZoneFlipped)
{
DesignerSafeZoneOverride = PlaySettings->FlipCustomUnsafeZones(CustomSafeZoneStarts, CustomSafeZoneDimensions, PreviewOverrideName, FVector2D(PreviewWidth, PreviewHeight));
}
else
{
DesignerSafeZoneOverride = PlaySettings->CalculateCustomUnsafeZones(CustomSafeZoneStarts, CustomSafeZoneDimensions, PreviewOverrideName, FVector2D(PreviewWidth, PreviewHeight));
}
for (int ZoneIndex = 0; ZoneIndex < CustomSafeZoneStarts.Num(); ZoneIndex++)
{
FVector2D Start = CustomSafeZoneStarts[ZoneIndex];
FVector2D Dimensions = CustomSafeZoneDimensions[ZoneIndex];
FSlateDrawElement::MakeBox(
PaintArgs.OutDrawElements,
PaintArgs.Layer,
PreviewGeometry.ToPaintGeometry(Dimensions, FSlateLayoutTransform(Start)),
WhiteBrush,
ESlateDrawEffect::None,
UnsafeZoneColor
);
}
}
}
}
void SDesignerView::UpdatePreviewWidget(bool bForceUpdate)
{
UUserWidget* LatestPreviewWidget = BlueprintEditor.Pin()->GetPreview();
if ( LatestPreviewWidget != PreviewWidget || bForceUpdate )
{
PreviewWidget = LatestPreviewWidget;
if ( PreviewWidget )
{
// Notify all designer extensions that a new preview was created
for (const TSharedRef<FDesignerExtension>& Ext : DesignerExtensions)
{
Ext->PreviewContentCreated(PreviewWidget);
}
TSharedRef<SWidget> NewPreviewSlateWidget = PreviewWidget->TakeWidget();
NewPreviewSlateWidget->SlatePrepass(PreviewSizeConstraint->GetCachedGeometry().Scale);
PreviewSlateWidget = NewPreviewSlateWidget;
PreviewSizeConstraint->SetContent(NewPreviewSlateWidget);
// Notify all designer extensions that the content has changed
for (const TSharedRef<FDesignerExtension>& Ext : DesignerExtensions)
{
Ext->PreviewContentChanged(NewPreviewSlateWidget);
}
// Notify all selected widgets that they are selected, because there are new preview objects
// state may have been lost so this will recreate it if the widget does something special when
// selected.
for ( const FWidgetReference& WidgetRef : GetSelectedWidgets() )
{
if ( WidgetRef.IsValid() )
{
WidgetRef.GetPreview()->SelectByDesigner();
}
}
BroadcastDesignerChanged();
}
else
{
ChildSlot
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("NoWidgetPreview", "No Widget Preview"))
]
];
}
}
}
void SDesignerView::BroadcastDesignerChanged()
{
UUserWidget* LatestPreviewWidget = BlueprintEditor.Pin()->GetPreview();
ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault<ULevelEditorPlaySettings>();
if ( LatestPreviewWidget )
{
FDesignerChangedEventArgs EventArgs;
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
switch ( DefaultWidget->DesignSizeMode )
{
case EDesignPreviewSizeMode::CustomOnScreen:
case EDesignPreviewSizeMode::DesiredOnScreen:
case EDesignPreviewSizeMode::FillScreen:
EventArgs.bScreenPreview = true;
break;
default:
EventArgs.bScreenPreview = false;
}
}
EventArgs.Size = FVector2D(PreviewWidth, PreviewHeight);
EventArgs.DpiScale = GetPreviewDPIScale();
LatestPreviewWidget->OnDesignerChanged(EventArgs);
}
}
void SDesignerView::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
UUserWidget* DefaultWidget = GetDefaultWidget();
if ( DefaultWidget && ( DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::CustomOnScreen || DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::DesiredOnScreen ) )
{
PreviewAreaConstraint->SetHAlign(HAlign_Left);
PreviewAreaConstraint->SetVAlign(VAlign_Top);
}
else
{
PreviewAreaConstraint->SetHAlign(HAlign_Fill);
PreviewAreaConstraint->SetVAlign(VAlign_Fill);
}
// Tick the parent first to update CachedGeometry
SDesignSurface::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
const bool bForceUpdate = false;
UpdatePreviewWidget(bForceUpdate);
// Perform an arrange children pass to cache the geometry of all widgets so that we can query it later.
{
CachedWidgetGeometry.Reset();
FArrangedWidget WindowWidgetGeometry(PreviewHitTestRoot.ToSharedRef(), GetDesignerGeometry());
PopulateWidgetGeometryCache(WindowWidgetGeometry);
}
TArray< TFunction<void()> >& QueuedActions = BlueprintEditor.Pin()->GetQueuedDesignerActions();
for ( TFunction<void()>& Action : QueuedActions )
{
Action();
}
if ( QueuedActions.Num() > 0 )
{
QueuedActions.Reset();
CachedWidgetGeometry.Reset();
FArrangedWidget WindowWidgetGeometry(PreviewHitTestRoot.ToSharedRef(), GetDesignerGeometry());
PopulateWidgetGeometryCache(WindowWidgetGeometry);
}
// Tick all designer extensions in case they need to update widgets
for ( const TSharedRef<FDesignerExtension>& Ext : DesignerExtensions )
{
Ext->Tick(GetDesignerGeometry(), InCurrentTime, InDeltaTime);
}
// Compute the origin in absolute space.
FGeometry RootGeometry = CachedWidgetGeometry.FindChecked(PreviewSurface.ToSharedRef()).Geometry;
FVector2D AbsoluteOrigin = MakeGeometryWindowLocal(RootGeometry).LocalToAbsolute(FVector2D::ZeroVector);
GridOrigin = AbsoluteOrigin;
TopRuler->SetRuling(AbsoluteOrigin, 1.0f / GetPreviewScale());
SideRuler->SetRuling(AbsoluteOrigin, 1.0f / GetPreviewScale());
if ( IsHovered() )
{
// Get cursor in absolute window space.
FVector2D CursorPos = FSlateApplication::Get().GetCursorPos();
CursorPos = MakeGeometryWindowLocal(RootGeometry).LocalToAbsolute(RootGeometry.AbsoluteToLocal(CursorPos));
TopRuler->SetCursor(CursorPos);
SideRuler->SetCursor(CursorPos);
}
else
{
TopRuler->SetCursor(TOptional<FVector2D>());
SideRuler->SetCursor(TOptional<FVector2D>());
}
if ( PreviewWidget )
{
TSharedPtr<SWidget> CachedWidget = PreviewWidget->GetCachedWidget();
if ( CachedWidget.IsValid() )
{
CachedPreviewDesiredSize = CachedWidget->GetDesiredSize();
}
}
}
void SDesignerView::OnPaintBackground(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const
{
// In order to get material parameter collections to function properly, we need the editor preview world's Scene
// properly propagated through to any widgets that depend on that functionality.
if (TSharedPtr<FWidgetBlueprintEditor> BlueprintEditorPtr = BlueprintEditor.Pin())
{
if (FPreviewScene* Preview = BlueprintEditorPtr->GetPreviewScene())
{
if (FSceneInterface* Scene = Preview->GetWorld()->Scene)
{
FSlateApplication::Get().GetRenderer()->RegisterCurrentScene(Scene);
}
}
}
// Set a UI scale for materials to use as reference, done on a per-window basis since don't want to change global uniforms per element
if (SWindow* ParentWindow = OutDrawElements.GetPaintWindow())
{
ParentWindow->SetViewportScaleUIOverride(GetZoomAmount());
}
SDesignSurface::OnPaintBackground(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId);
if (bShowResolutionOutlines)
{
if (const UWidgetEditingProjectSettings* Settings = FWidgetBlueprintEditorUtils::GetRelevantSettings(BlueprintEditor))
{
for (const FDebugResolution& Resolution : Settings->DebugResolutions)
{
DrawResolution(Resolution, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId);
}
}
}
}
void SDesignerView::DrawResolution(const FDebugResolution& Resolution, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const
{
const float Scale = GetZoomAmount();
const FVector2D ZeroSpace = AllottedGeometry.AbsoluteToLocal(GridOrigin);
const FSlateBrush* WhiteBrush = FAppStyle::GetBrush("WhiteBrush");
FVector2D ResolutionSize(Resolution.Width, Resolution.Height);
FSlateDrawElement::MakeBox(
OutDrawElements,
LayerId,
AllottedGeometry.ToPaintGeometry(ResolutionSize * Scale, FSlateLayoutTransform(ZeroSpace)),
WhiteBrush,
ESlateDrawEffect::None,
Resolution.Color
);
FSlateFontInfo FontInfo = FAppStyle::GetFontStyle("UMGEditor.ResizeResolutionFont");
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
FString ResolutionString;
if (Resolution.Description.IsEmpty())
{
ResolutionString = FString::Printf(TEXT("%d x %d"), Resolution.Width, Resolution.Height);
}
else
{
ResolutionString = FString::Printf(TEXT("%d x %d - %s"), Resolution.Width, Resolution.Height, *Resolution.Description);
}
FVector2D ResolutionStringSize = FontMeasureService->Measure(ResolutionString, FontInfo);
FSlateDrawElement::MakeText(
OutDrawElements,
LayerId,
AllottedGeometry.ToOffsetPaintGeometry(ZeroSpace + ResolutionSize * Scale - (ResolutionStringSize + FVector2D(2, 2))),
ResolutionString,
FontInfo,
ESlateDrawEffect::None,
FLinearColor::Black);
}
FReply SDesignerView::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
typedef FSelectedWidgetDragDropOp::FDraggingWidgetReference FDragWidget;
SDesignSurface::OnDragDetected(MyGeometry, MouseEvent);
const TSet<FWidgetReference>& SelectedWidgets = GetSelectedWidgets();
if (SelectedWidgets.Num() > 0)
{
TArray<FDragWidget> DraggingWidgetCandidates;
// Clear any pending selected widgets, the user has already decided what widget they want.
PendingSelectedWidget = FWidgetReference();
for (const FWidgetReference& SelectedWidget : SelectedWidgets)
{
// Determine The offset to keep the widget from the mouse while dragging
FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry());
FDesignTimeUtils::GetArrangedWidget(SelectedWidget.GetPreview()->GetCachedWidget().ToSharedRef(), ArrangedWidget);
SelectedWidgetContextMenuLocation = ArrangedWidget.Geometry.AbsoluteToLocal(DraggingStartPositionScreenSpace);
FDragWidget DraggingWidget;
DraggingWidget.Widget = SelectedWidget;
DraggingWidget.DraggedOffset = SelectedWidgetContextMenuLocation / ArrangedWidget.Geometry.GetLocalSize();
DraggingWidgetCandidates.Add(DraggingWidget);
}
TArray<FDragWidget> DraggingWidgets;
for (const FDragWidget& Candidate : DraggingWidgetCandidates)
{
// check the parent chain of each dragged widget and ignore those that are children of other dragged widgets
bool bIsChild = false;
for (auto CursorPtr = Candidate.Widget.GetTemplate()->GetParent(); CursorPtr != nullptr; CursorPtr = CursorPtr->GetParent())
{
if (DraggingWidgetCandidates.ContainsByPredicate([CursorPtr](const FDragWidget& W){ return W.Widget.GetTemplate() == CursorPtr; }))
{
bIsChild = true;
break;
}
}
if (!bIsChild)
{
DraggingWidgets.Add(Candidate);
}
}
ClearExtensionWidgets();
TSharedRef<FSelectedWidgetDragDropOp> DragOp = FSelectedWidgetDragDropOp::New(BlueprintEditor.Pin(), this, DraggingWidgets);
TWeakPtr<SDesignerView> WeakDesignerView = SharedThis(this);
DragOp->OnDragDropEnded.AddLambda([WeakDesignerView, WeakDragOp = DragOp.ToWeakPtr()]()
{
if (TSharedPtr<SDesignerView> DesignerViewPtr = WeakDesignerView.Pin())
{
if (TSharedPtr<FWidgetBlueprintEditor> BlueprintEditorPtr = DesignerViewPtr->BlueprintEditor.Pin())
{
if (DesignerViewPtr->DropPreviews.Num() == 0)
{
DesignerViewPtr->bMovingExistingWidget = false;
if (WeakDragOp.IsValid() && WeakDragOp.Pin()->DraggedWidgets.Num() > 0)
{
BlueprintEditorPtr->RefreshPreview();
}
}
}
}
});
return FReply::Handled().BeginDragDrop(DragOp);
}
return FReply::Unhandled();
}
void SDesignerView::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
SDesignSurface::OnDragEnter(MyGeometry, DragDropEvent);
BlueprintEditor.Pin()->ClearHoveredWidget();
//@TODO UMG Drop Feedback
}
void SDesignerView::OnDragLeave(const FDragDropEvent& DragDropEvent)
{
SDesignSurface::OnDragLeave(DragDropEvent);
BlueprintEditor.Pin()->ClearHoveredWidget();
TSharedPtr<FDecoratedDragDropOp> DecoratedDragDropOp = DragDropEvent.GetOperationAs<FDecoratedDragDropOp>();
if ( DecoratedDragDropOp.IsValid() )
{
DecoratedDragDropOp->SetCursorOverride(TOptional<EMouseCursor::Type>());
DecoratedDragDropOp->ResetToDefaultToolTip();
}
ClearDropPreviews();
}
FReply SDesignerView::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
SDesignSurface::OnDragOver(MyGeometry, DragDropEvent);
const bool bIsPreview = true;
bool bFoundChangingParent = false;
TSharedPtr<FSelectedWidgetDragDropOp> SelectedDragDropOp = DragDropEvent.GetOperationAs<FSelectedWidgetDragDropOp>();
if (SelectedDragDropOp.IsValid())
{
for (const auto& DraggedWidget : SelectedDragDropOp->DraggedWidgets)
{
if (!DraggedWidget.bStayingInParent)
{
bFoundChangingParent = true;
break;
}
}
if (bFoundChangingParent)
{
ClearDropPreviews();
ProcessDropAndAddWidget(MyGeometry, DragDropEvent, bIsPreview);
}
else
{
MoveWidgets(MyGeometry, DragDropEvent, bIsPreview, nullptr, false);
}
}
else
{
ClearDropPreviews();
ProcessDropAndAddWidget(MyGeometry, DragDropEvent, bIsPreview);
}
if ( DropPreviews.Num() > 0 )
{
//@TODO UMG Drop Feedback
return FReply::Handled();
}
return FReply::Unhandled();
}
void SDesignerView::DetermineDragDropPreviewWidgets(TArray<UWidget*>& OutWidgets, const FDragDropEvent& DragDropEvent, UWidgetTree* RootWidgetTree)
{
OutWidgets.Empty();
UWidgetBlueprint* Blueprint = GetBlueprint();
if (RootWidgetTree == nullptr)
{
return;
}
TSharedPtr<FDragDropOperation> DragDropOp = DragDropEvent.GetOperation();
UWidget* Widget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, RootWidgetTree, DragDropOp);
if (Widget)
{
OutWidgets.Add(Widget);
}
// Mark the widgets for design-time rendering
for (UWidget* OutWidget : OutWidgets)
{
OutWidget->SetDesignerFlags(BlueprintEditor.Pin()->GetCurrentDesignerFlags());
}
}
void SDesignerView::SwapSafeZoneTypes()
{
if (FDisplayMetrics::GetDebugTitleSafeZoneRatio() < 1.f)
{
PreviewOverrideName = FString();
}
}
void SDesignerView::ProcessDropAndAddWidget(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent, const bool bIsPreview)
{
TSharedPtr<FDragDropOperation> DragOperation = DragDropEvent.GetOperation();
// In order to prevent the GetWidgetAtCursor code from picking the widgets we're about to move, we need to mark them
// as the drop preview widgets before any other code can run.
TSharedPtr<FSelectedWidgetDragDropOp> SelectedDragDropOp = DragDropEvent.GetOperationAs<FSelectedWidgetDragDropOp>();
if (SelectedDragDropOp.IsValid())
{
DropPreviews.Empty();
for (const auto& DraggedWidget : SelectedDragDropOp->DraggedWidgets)
{
FDropPreview DropPreview;
DropPreview.Parent = nullptr;
DropPreview.Widget = DraggedWidget.Preview;
DropPreview.DragOperation = DragOperation;
DropPreviews.Add(DropPreview);
}
}
ClearDropPreviews();
UWidgetBlueprint* BP = GetBlueprint();
UWidget* Target = nullptr;
UWidgetTree* TargetTree = nullptr;
FWidgetHitResult HitResult;
if (FindWidgetUnderCursor(MyGeometry, DragDropEvent, UPanelWidget::StaticClass(), HitResult))
{
Target = bIsPreview ? HitResult.Widget.GetPreview() : HitResult.Widget.GetTemplate();
TargetTree = (bIsPreview && Target) ? Cast<UWidgetTree>(Target->GetOuter()) : ToRawPtr(BP->WidgetTree);
}
else if (BP->WidgetTree->RootWidget == nullptr || !bIsPreview)
{
TargetTree = BP->WidgetTree;
}
FGeometry WidgetUnderCursorGeometry = HitResult.WidgetArranged.Geometry;
FScopedTransaction DragAndDropTransaction(LOCTEXT("Designer_DragAddDrop", "Drag and Drop Widget"));
TArray<UWidget*> DragDropPreviewWidgets;
if ( SelectedDragDropOp.IsValid() && SelectedDragDropOp->Designer != this)
{
// Only accept drag drop from the same editor
SelectedDragDropOp.Reset();
}
else
{
DetermineDragDropPreviewWidgets(DragDropPreviewWidgets, DragDropEvent, TargetTree);
}
if ( DragDropPreviewWidgets.Num() > 0 )
{
BlueprintEditor.Pin()->SetHoveredWidget(HitResult.Widget);
DragOperation->SetCursorOverride(TOptional<EMouseCursor::Type>());
FScopedTransaction Transaction(LOCTEXT("Designer_AddWidget", "Add Widget"));
FText DropOnTargetFailureText = FText::GetEmpty();
const bool bShouldPreventDropOnTargetExtensions = FWidgetBlueprintEditorUtils::ShouldPreventDropOnTargetExtensions(Target, SelectedDragDropOp, DropOnTargetFailureText);
if ( bShouldPreventDropOnTargetExtensions )
{
DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle);
DragAndDropTransaction.Cancel();
}
// If there's no root widget go ahead and add the widget into the root slot.
else if ( BP->WidgetTree->RootWidget == nullptr )
{
if ( !bIsPreview )
{
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
}
// TODO UMG This method isn't great, maybe the user widget should just be a canvas.
// Add it to the root if there are no other widgets to add it to.
BP->WidgetTree->RootWidget = DragDropPreviewWidgets[0];
for (UWidget* Widget : DragDropPreviewWidgets)
{
FDropPreview DropPreview;
DropPreview.Widget = Widget;
DropPreview.Parent = nullptr;
DropPreview.DragOperation = DragOperation;
DropPreviews.Add(DropPreview);
}
if (!bIsPreview && BP->WidgetTree->RootWidget)
{
// We've added a new widget to the BP, notify the BP so it can create a GUID to track it
BP->OnVariableAdded(BP->WidgetTree->RootWidget->GetFName());
}
}
// If there's already a root widget we need to try and place our widget into a parent widget that we've picked against
else if ( Target && Target->IsA(UPanelWidget::StaticClass()) )
{
UPanelWidget* Parent = Cast<UPanelWidget>(Target);
// If this isn't a preview operation we need to modify a few things to properly undo the operation.
if ( !bIsPreview )
{
Parent->SetFlags(RF_Transactional);
Parent->Modify();
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
}
// Determine local position inside the parent widget and add the widget to the slot.
FVector2D LocalPosition = WidgetUnderCursorGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition());
for (UWidget* Widget : DragDropPreviewWidgets)
{
if (UPanelSlot* Slot = Parent->AddChild(Widget))
{
const UWidgetDesignerSettings* const WidgetDesignerSettings = GetDefault<UWidgetDesignerSettings>();
const TOptional<int32> GridSnapSize = WidgetDesignerSettings->GridSnapEnabled ? TOptional<int32>(WidgetDesignerSettings->GridSnapSize) : TOptional<int32>();
Slot->DragDropPreviewByDesigner(LocalPosition, GridSnapSize, GridSnapSize);
FDropPreview DropPreview;
DropPreview.Widget = Widget;
DropPreview.Parent = Parent;
DropPreview.DragOperation = DragOperation;
DropPreviews.Add(DropPreview);
if (!bIsPreview && Widget)
{
// We've added a new widget to the BP, notify the BP so it can create a GUID to track it
BP->OnVariableAdded(Widget->GetFName());
}
}
else
{
// Too many children. Stop processing them.
if (Widget == DragDropPreviewWidgets[0])
{
DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle);
}
break;
// TODO UMG ERROR Slot can not be created because maybe the max children has been reached.
// Maybe we can traverse the hierarchy and add it to the first parent that will accept it?
}
}
}
else
{
DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle);
// Cancel the transaction even if it's not a preview, since we can't do anything
DragAndDropTransaction.Cancel();
}
if (bIsPreview)
{
DragAndDropTransaction.Cancel();
}
// Remove widgets tracked by the 'DropPreviews' set. We don't consider them to be transient at this point because they have been inserted into the widget tree hierarchy.
for (const FDropPreview& DropPreview : DropPreviews)
{
DragDropPreviewWidgets.RemoveSwap(DropPreview.Widget);
}
// Move the remaining widgets into the transient package. Otherwise, they will remain outered to the WidgetTree and end up as properties in the BP class layout as a result.
for (UWidget* Widget : DragDropPreviewWidgets)
{
if (Widget->GetOutermost() != GetTransientPackage())
{
Widget->SetFlags(RF_NoFlags);
Widget->Rename(nullptr, GetTransientPackage());
}
}
// If we had preview widgets, we know that we can not be performing a selected widget drag/drop operation. Bail.
return;
}
// Attempt to deal with moving widgets from a drag operation.
MoveWidgets(MyGeometry, DragDropEvent, bIsPreview, Target, true);
}
void SDesignerView::MoveWidgets(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent, const bool bIsPreview, UWidget* Target, const bool bAnyWidgetChangingParent)
{
TSharedPtr<FSelectedWidgetDragDropOp> SelectedDragDropOp = DragDropEvent.GetOperationAs<FSelectedWidgetDragDropOp>();
TSharedPtr<FDragDropOperation> DragOperation = DragDropEvent.GetOperation();
FScopedTransaction DragAndDropTransaction(LOCTEXT("Designer_DragAddDrop", "Drag and Drop Widget"));
if (SelectedDragDropOp.IsValid() && SelectedDragDropOp->DraggedWidgets.Num() > 0)
{
SelectedDragDropOp->SetCursorOverride(TOptional<EMouseCursor::Type>());
FText TransactionText;
if (SelectedDragDropOp->DraggedWidgets.Num() == 1)
{
TransactionText = LOCTEXT("Designer_MoveWidget", "Move Widget");
}
else
{
TransactionText = LOCTEXT("Designer_MoveWidgets", "Move Widgets");
}
FScopedTransaction Transaction(TransactionText);
bool bWidgetMoved = false;
for (auto& DraggedWidget : SelectedDragDropOp->DraggedWidgets)
{
// If they've pressed alt, and we were staying in the parent, disable that
// and adjust the designer message to no longer warn.
if (DragDropEvent.IsAltDown() && DraggedWidget.bStayingInParent)
{
DraggedWidget.bStayingInParent = false;
if (SelectedDragDropOp->bShowingMessage)
{
SelectedDragDropOp->bShowingMessage = false;
PopDesignerMessage();
}
}
FWidgetHitResult HitResult;
FGeometry WidgetUnderCursorGeometry;
bool bFoundWidgetUnderCursor = FindWidgetUnderCursor(MyGeometry, DragDropEvent, UPanelWidget::StaticClass(), HitResult);
// If we're staying in the parent we started in, replace the parent found under the cursor with
// the original one, also update the arranged widget data so that our layout calculations are accurate.
UWidgetBlueprint* BP = GetBlueprint();
if (DraggedWidget.bStayingInParent)
{
// If we are not changing parents, keep the widget in the hierarchy but clean the DropPreviews list.
if (!bAnyWidgetChangingParent)
{
DropPreviews.Empty();
}
WidgetUnderCursorGeometry = GetDesignerGeometry();
if (GetWidgetGeometry(DraggedWidget.ParentWidget, WidgetUnderCursorGeometry))
{
Target = bIsPreview ? DraggedWidget.ParentWidget.GetPreview() : DraggedWidget.ParentWidget.GetTemplate();
}
}
else if (bFoundWidgetUnderCursor)
{
WidgetUnderCursorGeometry = HitResult.WidgetArranged.Geometry;
}
// If we changed the value of bStayingInParent to false since the last check, remove this widget from the hierarchy and later determine a new parent for it.
if (!DraggedWidget.bStayingInParent && !bAnyWidgetChangingParent)
{
ClearDropPreviews();
Target = bIsPreview ? HitResult.Widget.GetPreview() : HitResult.Widget.GetTemplate();
}
FWidgetReference TargetReference = bIsPreview ? BlueprintEditor.Pin()->GetReferenceFromPreview(Target) : BlueprintEditor.Pin()->GetReferenceFromTemplate(Target);
BlueprintEditor.Pin()->SetHoveredWidget(TargetReference);
FText DropOnTargetFailureText = FText::GetEmpty();
const bool bShouldPreventDropOnTargetExtensions = FWidgetBlueprintEditorUtils::ShouldPreventDropOnTargetExtensions(Target, SelectedDragDropOp, DropOnTargetFailureText);
// If the widget being hovered over is a panel, attempt to place it into that panel.
if (Target && Target->IsA(UPanelWidget::StaticClass()) && !bShouldPreventDropOnTargetExtensions)
{
bWidgetMoved = true;
UWidget* Widget = bIsPreview ? DraggedWidget.Preview : DraggedWidget.Template;
UWidget* ParentWidget = bIsPreview ? DraggedWidget.ParentWidget.GetPreview() : DraggedWidget.ParentWidget.GetTemplate();
if (ensure(Widget))
{
UPanelWidget* NewParent = Cast<UPanelWidget>(Target);
const bool bIsChangingParent = ParentWidget != Target;
if (bIsChangingParent)
{
check(ParentWidget != nullptr);
UBlueprint* OriginalBP = nullptr;
// If this isn't a preview operation we need to modify a few things to properly undo the operation.
if (!bIsPreview)
{
NewParent->SetFlags(RF_Transactional);
NewParent->Modify();
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
// If the Widget is changing parents, there's a chance it might be moving to a different WidgetTree as well.
UWidgetTree* OriginalWidgetTree = Cast<UWidgetTree>(Widget->GetOuter());
if (UWidgetTree::TryMoveWidgetToNewTree(Widget, BP->WidgetTree))
{
// The Widget likely originated from a different blueprint, so get what blueprint it was originally a part of.
OriginalBP = OriginalWidgetTree ? OriginalWidgetTree->GetTypedOuter<UBlueprint>() : nullptr;
}
Widget->SetFlags(RF_Transactional);
Widget->Modify();
ParentWidget->SetFlags(RF_Transactional);
ParentWidget->Modify();
}
// The Widget originated from a different blueprint, so mark it as modified.
if (OriginalBP && OriginalBP != BP)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(OriginalBP);
}
}
else if (!bIsPreview)
{
Widget->SetFlags(RF_Transactional);
Widget->Modify();
ParentWidget->SetFlags(RF_Transactional);
ParentWidget->Modify();
}
FVector2D ScreenSpacePosition = DragDropEvent.GetScreenSpacePosition();
const UWidgetDesignerSettings* DesignerSettings = GetDefault<UWidgetDesignerSettings>();
bool bGridSnapX, bGridSnapY;
bGridSnapX = bGridSnapY = DesignerSettings->GridSnapEnabled;
// As long as shift is pressed and we're staying in the same parent,
// allow the user to lock the movement to a specific axis.
const bool bLockToAxis =
FSlateApplication::Get().GetModifierKeys().IsShiftDown() &&
DraggedWidget.bStayingInParent;
if (bLockToAxis)
{
// Choose the largest axis of movement as the primary axis to lock to.
FVector2D DragDelta = ScreenSpacePosition - DraggingStartPositionScreenSpace;
if (FMath::Abs(DragDelta.X) > FMath::Abs(DragDelta.Y))
{
// Lock to X Axis
ScreenSpacePosition.Y = DraggingStartPositionScreenSpace.Y;
bGridSnapY = false;
}
else
{
// Lock To Y Axis
ScreenSpacePosition.X = DraggingStartPositionScreenSpace.X;
bGridSnapX = false;
}
}
FVector2D LocalPosition = WidgetUnderCursorGeometry.AbsoluteToLocal(ScreenSpacePosition);
UPanelSlot* Slot = nullptr;
// Determine if we need to create a new slot or fetch an existing one.
// Fetching is much faster, so we want to avoid creating whenever possible.
if (bAnyWidgetChangingParent || bIsChangingParent)
{
if (bIsChangingParent)
{
Slot = NewParent->AddChild(Widget);
}
else if (UPanelWidget* ParentWidgetAsPanel = Cast<UPanelWidget>(ParentWidget))
{
Slot = ParentWidgetAsPanel->InsertChildAt(ParentWidgetAsPanel->GetChildIndex(Widget), Widget);
}
}
else
{
if (UPanelWidget* ParentWidgetAsPanel = Cast<UPanelWidget>(ParentWidget))
{
Slot = Widget->Slot;
// If we expected to find a slot but it's null, we have to create it.
if (Slot == nullptr)
{
Slot = ParentWidgetAsPanel->AddChild(Widget);
}
}
}
if (Slot != nullptr)
{
FWidgetBlueprintEditorUtils::ImportPropertiesFromText(Slot, DraggedWidget.ExportedSlotProperties);
bool HasChangedLayout = false;
// HACK UMG: In order to correctly drop items into the canvas that have a non-zero anchor,
// we need to know the layout information after slate has performed a pre-pass. So we have
// to rebase the layout and reinterpret the new position based on anchor point layout data.
// This should be pulled out into an extension of some kind so that this can be fixed for
// other widgets as well that may need to do work like this.
if (UCanvasPanelSlot* CanvasSlot = Cast<UCanvasPanelSlot>(Slot))
{
if (bIsPreview)
{
CanvasSlot->SaveBaseLayout();
FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry());
FDesignTimeUtils::GetArrangedWidget(Widget->GetCachedWidget().ToSharedRef(), ArrangedWidget);
FVector2D Offset = FVector2D::ZeroVector;
if (TSharedPtr<SWidget> CachedWidget = Widget->GetCachedWidget())
{
FDesignTimeUtils::GetArrangedWidget(CachedWidget.ToSharedRef(), ArrangedWidget);
Offset = DraggedWidget.DraggedOffset * ArrangedWidget.Geometry.GetLocalSize();
}
FVector2D NewPosition;
if (bAnyWidgetChangingParent || bIsChangingParent)
{
NewPosition = LocalPosition - Offset;
}
else
{
NewPosition = LocalPosition - DragDropEvent.GetCursorDelta() - Offset;
}
// Perform grid snapping on X and Y if we need to.
if (bGridSnapX)
{
NewPosition.X = ((int32)NewPosition.X) - (((int32)NewPosition.X) % DesignerSettings->GridSnapSize);
}
if (bGridSnapY)
{
NewPosition.Y = ((int32)NewPosition.Y) - (((int32)NewPosition.Y) % DesignerSettings->GridSnapSize);
}
CanvasSlot->SetDesiredPosition(NewPosition);
CanvasSlot->RebaseLayout();
HasChangedLayout = true;
}
}
else
{
const TOptional<int32> XGridSnapSize = bGridSnapX ? TOptional<int32>(DesignerSettings->GridSnapSize) : TOptional<int32>();
const TOptional<int32> YGridSnapSize = bGridSnapY ? TOptional<int32>(DesignerSettings->GridSnapSize) : TOptional<int32>();
HasChangedLayout = Slot->DragDropPreviewByDesigner(LocalPosition, XGridSnapSize, YGridSnapSize);
}
// Re-export slot properties.
if (HasChangedLayout)
{
FWidgetBlueprintEditorUtils::ExportPropertiesToText(Slot, DraggedWidget.ExportedSlotProperties);
}
FDropPreview DropPreview;
DropPreview.Widget = Widget;
DropPreview.Parent = NewParent;
DropPreviews.Add(DropPreview);
}
else
{
SelectedDragDropOp->SetCursorOverride(EMouseCursor::SlashedCircle);
// TODO UMG ERROR Slot can not be created because maybe the max children has been reached.
// Maybe we can traverse the hierarchy and add it to the first parent that will accept it?
}
}
}
else
{
SelectedDragDropOp->SetCursorOverride(EMouseCursor::SlashedCircle);
}
}
if (bIsPreview || !bWidgetMoved)
{
DragAndDropTransaction.Cancel();
}
}
// Either we're not dragging anything, or no widgets were valid...
if (DropPreviews.Num() == 0)
{
DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle);
}
}
FReply SDesignerView::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
SDesignSurface::OnDrop(MyGeometry, DragDropEvent);
bMovingExistingWidget = false;
const bool bIsPreview = false;
bool bFoundChangingParent = false;
TSharedPtr<FSelectedWidgetDragDropOp> SelectedDragDropOp = DragDropEvent.GetOperationAs<FSelectedWidgetDragDropOp>();
if (SelectedDragDropOp.IsValid())
{
for (const auto& DraggedWidget : SelectedDragDropOp->DraggedWidgets)
{
if (!DraggedWidget.bStayingInParent)
{
bFoundChangingParent = true;
break;
}
}
if (bFoundChangingParent)
{
ClearDropPreviews();
ProcessDropAndAddWidget(MyGeometry, DragDropEvent, bIsPreview);
}
else
{
MoveWidgets(MyGeometry, DragDropEvent, bIsPreview, nullptr, false);
}
}
else
{
bFoundChangingParent = true;
ClearDropPreviews();
ProcessDropAndAddWidget(MyGeometry, DragDropEvent, bIsPreview);
}
if (DropPreviews.Num() > 0)
{
UWidgetBlueprint* BP = GetBlueprint();
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
TSet<FWidgetReference> SelectedTemplates;
for (const auto& DropPreview : DropPreviews)
{
SelectedTemplates.Add(BlueprintEditor.Pin()->GetReferenceFromTemplate(DropPreview.Widget));
}
BlueprintEditor.Pin()->SelectWidgets(SelectedTemplates, false);
// Regenerate extension widgets now that we've finished moving or placing the widget.
CreateExtensionWidgetsForSelection();
DropPreviews.Empty();
return FReply::Handled().SetUserFocus(SharedThis(this));
}
else if (SelectedDragDropOp.IsValid())
{
// If we were dragging any widgets, even if we didn't move them, we need to refresh the preview
// because they are collapsed in the preview when the drag begins
if (SelectedDragDropOp->DraggedWidgets.Num() > 0)
{
BlueprintEditor.Pin().Get()->RefreshPreview();
}
}
return FReply::Unhandled();
}
FText SDesignerView::GetResolutionText(int32 Width, int32 Height, const FString& AspectRatio) const
{
FFormatNamedArguments Args;
Args.Add(TEXT("Width"), FText::AsNumber(Width, &FNumberFormattingOptions::DefaultNoGrouping()));
Args.Add(TEXT("Height"), FText::AsNumber(Height, &FNumberFormattingOptions::DefaultNoGrouping()));
Args.Add(TEXT("AspectRatio"), FText::FromString(AspectRatio));
return FText::Format(LOCTEXT("CommonResolutionFormat", "{Width} x {Height} ({AspectRatio})"), Args);
}
FText SDesignerView::GetCurrentResolutionText() const
{
return GetResolutionText(PreviewWidth, PreviewHeight, PreviewAspectRatio);
}
FText SDesignerView::GetCurrentDPIScaleText() const
{
FNumberFormattingOptions Options = FNumberFormattingOptions::DefaultNoGrouping();
Options.MinimumIntegralDigits = 1;
Options.MaximumFractionalDigits = 2;
Options.MinimumFractionalDigits = 1;
const UUserInterfaceSettings* UISettings = GetDefault<UUserInterfaceSettings>();
if (UISettings->UIScaleRule == EUIScalingRule::Custom)
{
UClass* CustomScalingRuleClassInstance = UISettings->CustomScalingRuleClass.TryLoadClass<UDPICustomScalingRule>();
if (CustomScalingRuleClassInstance == nullptr)
{
return LOCTEXT("NoCustomRuleWarning", "Warning: Using Custom DPI Rule with no rules class set. Set a class in User Interface Project Settings. ");
}
}
FText DPIString = FText::AsNumber(GetPreviewDPIScale(), &Options);
return FText::Format(LOCTEXT("CurrentDPIScaleFormat", "DPI Scale {0}"), DPIString);
}
FSlateColor SDesignerView::GetCurrentDPIScaleColor() const
{
const UUserInterfaceSettings* UISettings = GetDefault<UUserInterfaceSettings>();
if (UISettings->UIScaleRule == EUIScalingRule::Custom)
{
UClass* CustomScalingRuleClassInstance = UISettings->CustomScalingRuleClass.TryLoadClass<UDPICustomScalingRule>();
if (CustomScalingRuleClassInstance == nullptr)
{
return FSlateColor(FLinearColor::Yellow);
}
}
return FSlateColor(FLinearColor(1, 1, 1, 0.25f));
}
FText SDesignerView::GetCurrentScaleFactorText() const
{
FNumberFormattingOptions Options = FNumberFormattingOptions::DefaultNoGrouping();
Options.MinimumIntegralDigits = 1;
Options.MaximumFractionalDigits = 2;
Options.MinimumFractionalDigits = 1;
FText DPIString = FText::AsNumber(ScaleFactor, &Options);
return FText::Format(LOCTEXT("CurrentContentScale", "Device Content Scale {0}"), DPIString);
}
FText SDesignerView::GetCurrentSafeZoneText() const
{
if (PreviewOverrideName.IsEmpty())
{
float SafeZone = FDisplayMetrics::GetDebugTitleSafeZoneRatio();
if (FMath::IsNearlyEqual(SafeZone, 1.0f))
{
return LOCTEXT("NoSafeZoneSet", "No Device Safe Zone Set");
}
return FText::Format(LOCTEXT("UniformSafeZone", "Uniform Safe Zone: {0}"), FText::AsNumber(SafeZone));
}
return FText::FromString(PreviewOverrideName);
}
FSlateColor SDesignerView::GetResolutionTextColorAndOpacity() const
{
return FLinearColor(1, 1, 1, 1.25f - ResolutionTextFade.GetLerp());
}
EVisibility SDesignerView::GetResolutionTextVisibility() const
{
// If we're using a custom design time size, don't bother showing the resolution
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
const bool bScreenlessSizing =
DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::Custom ||
DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::Desired;
if ( bScreenlessSizing )
{
return EVisibility::Collapsed;
}
}
return EVisibility::SelfHitTestInvisible;
}
EVisibility SDesignerView::GetDesignerOutlineVisibility() const
{
if ( GEditor->bIsSimulatingInEditor || GEditor->PlayWorld != nullptr )
{
return EVisibility::HitTestInvisible;
}
TSharedPtr<ISequencer> Sequencer = BlueprintEditor.Pin()->GetSequencer();
if ( Sequencer.IsValid() && Sequencer->GetAutoChangeMode() != EAutoChangeMode::None )
{
return EVisibility::HitTestInvisible;
}
if ( Sequencer.IsValid() )
{
UWidgetAnimation* WidgetAnimation = Cast<UWidgetAnimation>(Sequencer->GetFocusedMovieSceneSequence());
if ( WidgetAnimation != UWidgetAnimation::GetNullAnimation() )
{
return EVisibility::HitTestInvisible;
}
}
return EVisibility::Hidden;
}
FSlateColor SDesignerView::GetDesignerOutlineColor() const
{
if ( GEditor->bIsSimulatingInEditor || GEditor->PlayWorld != nullptr )
{
FLinearColor SimulatingIndicatorColor = FLinearColor(0.863f, 0.407, 0.0f);
return SimulatingIndicatorColor;
}
TSharedPtr<ISequencer> Sequencer = BlueprintEditor.Pin()->GetSequencer();
if ( Sequencer.IsValid() && Sequencer->GetAutoChangeMode() != EAutoChangeMode::None )
{
FLinearColor AnimRecordingIndicatorColor = FLinearColor::FromSRGBColor(FColor(251, 37, 0));
return AnimRecordingIndicatorColor;
}
if ( Sequencer.IsValid() )
{
UWidgetAnimation* WidgetAnimation = Cast<UWidgetAnimation>(Sequencer->GetFocusedMovieSceneSequence());
if ( WidgetAnimation != UWidgetAnimation::GetNullAnimation() )
{
FLinearColor AnimSelectedIndicatorColor = FLinearColor::FromSRGBColor(FColor(0, 67, 240));
return AnimSelectedIndicatorColor;
}
}
return FLinearColor::Transparent;
}
FText SDesignerView::GetDesignerOutlineText() const
{
if ( GEditor->bIsSimulatingInEditor || GEditor->PlayWorld != nullptr )
{
return LOCTEXT("SIMULATING", "SIMULATING");
}
TSharedPtr<ISequencer> Sequencer = BlueprintEditor.Pin()->GetSequencer();
if ( Sequencer.IsValid() && Sequencer->GetAutoChangeMode() != EAutoChangeMode::None )
{
return LOCTEXT("RECORDING", "RECORDING");
}
if ( Sequencer.IsValid() )
{
UWidgetAnimation* WidgetAnimation = Cast<UWidgetAnimation>(Sequencer->GetFocusedMovieSceneSequence());
if ( WidgetAnimation != UWidgetAnimation::GetNullAnimation() )
{
FFormatNamedArguments Args;
Args.Add(TEXT("Name"), FText::FromString(WidgetAnimation->GetDisplayLabel()));
return FText::Format(LOCTEXT("SELECTED", "SELECTED: {Name}"), Args);
}
}
return FText::GetEmpty();
}
FText SDesignerView::GetCursorPositionText() const
{
if (const FArrangedWidget* CachedPreviewSurface = CachedWidgetGeometry.Find(PreviewSurface.ToSharedRef()))
{
const FGeometry& RootGeometry = CachedPreviewSurface->Geometry;
const FVector2D CursorPos = RootGeometry.AbsoluteToLocal(FSlateApplication::Get().GetCursorPos()) / GetPreviewDPIScale();
return FText::Format(LOCTEXT("CursorPositionFormat", "{0} x {1}"), FText::AsNumber(FMath::RoundToInt(CursorPos.X)), FText::AsNumber(FMath::RoundToInt(CursorPos.Y)));
}
return FText();
}
EVisibility SDesignerView::GetCursorPositionTextVisibility() const
{
return IsHovered() ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed;
}
FText SDesignerView::GetSelectedWidgetDimensionsText() const
{
const FWidgetReference SelectedWidget = GetSelectedWidget();
if ( SelectedWidget.IsValid() )
{
const UWidget* WidgetPreview = SelectedWidget.GetPreview();
const FVector2D& Size = WidgetPreview->GetCachedGeometry().GetLocalSize();
FNumberFormattingOptions FmtOptions;
FmtOptions.SetMaximumFractionalDigits(2);
const FText ScaleFactorText = FText::Format(
LOCTEXT("SelectionDimensionsScaleFormat", "(Render Scale: {0} x {1})"),
FText::AsNumber(WidgetPreview->GetRenderTransform().Scale.X, &FmtOptions),
FText::AsNumber(WidgetPreview->GetRenderTransform().Scale.Y, &FmtOptions));
bool bShowScaleFactor = !FMath::IsNearlyEqual(WidgetPreview->GetRenderTransform().Scale.X, 1.f) || !FMath::IsNearlyEqual(WidgetPreview->GetRenderTransform().Scale.Y, 1.f);
return FText::Format(
LOCTEXT("SelectionDimensionsFormat", "Selection: {0} x {1} {2}"),
FText::AsNumber(Size.X, &FmtOptions),
FText::AsNumber(Size.Y, &FmtOptions),
bShowScaleFactor ? ScaleFactorText : FText());
}
return FText();
}
EVisibility SDesignerView::GetSelectedWidgetDimensionsVisibility() const
{
const TSet<FWidgetReference>& SelectedWidgets = BlueprintEditor.Pin()->GetSelectedWidgets();
return SelectedWidgets.Num() == 1 ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed;
}
FReply SDesignerView::HandleDPISettingsClicked()
{
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Project", "Engine", "UI");
return FReply::Handled();
}
void SDesignerView::HandleOnCommonResolutionSelected(const FPlayScreenResolution InResolution)
{
bSafeZoneFlipped = false;
bCanPreviewSwapAspectRatio = InResolution.bCanSwapAspectRatio;
WidthReadFromSettings = InResolution.Width;
HeightReadFromSettings = InResolution.Height;
// Most resolutions (tablets, phones, TVs, etc.) can be stored in either portrait or landscape mode, and may need to be flipped
if (bCanPreviewSwapAspectRatio && (bPreviewIsPortrait != (InResolution.Width < InResolution.Height)))
{
PreviewWidth = InResolution.LogicalHeight;
PreviewHeight = InResolution.LogicalWidth;
}
else
{
PreviewWidth = InResolution.LogicalWidth;
PreviewHeight = InResolution.LogicalHeight;
}
bPreviewIsPortrait = PreviewWidth < PreviewHeight;
PreviewAspectRatio = InResolution.AspectRatio;
PreviewOverrideName = InResolution.ProfileName;
ScaleFactor = InResolution.ScaleFactor;
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewWidth"), PreviewWidth, GEditorPerProjectIni);
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewHeight"), PreviewHeight, GEditorPerProjectIni);
GConfig->SetString(*ConfigSectionName, TEXT("PreviewAspectRatio"), *PreviewAspectRatio, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bIsInPortraitMode"), bPreviewIsPortrait, GEditorPerProjectIni);
GConfig->SetString(*ConfigSectionName, TEXT("ProfileName"), *PreviewOverrideName, GEditorPerProjectIni);
GConfig->SetFloat(*ConfigSectionName, TEXT("ScaleFactor"), ScaleFactor, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bCanPreviewSwapAspectRatio"), bCanPreviewSwapAspectRatio, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bCommonResolutionSelected"), true, GEditorPerProjectIni);
if (!PreviewOverrideName.IsEmpty())
{
ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault<ULevelEditorPlaySettings>();
DesignerSafeZoneOverride = PlayInSettings->CalculateCustomUnsafeZones(CustomSafeZoneStarts, CustomSafeZoneDimensions, PreviewOverrideName, FVector2D(PreviewWidth, PreviewHeight));
}
else
{
FSlateApplication::Get().ResetCustomSafeZone();
FSlateApplication::Get().GetSafeZoneSize(DesignerSafeZoneOverride, FVector2D(PreviewWidth, PreviewHeight));
}
FMargin SafeZoneRatio = DesignerSafeZoneOverride;
SafeZoneRatio.Left /= (PreviewWidth / 2.0f);
SafeZoneRatio.Right /= (PreviewWidth / 2.0f);
SafeZoneRatio.Bottom /= (PreviewHeight / 2.0f);
SafeZoneRatio.Top /= (PreviewHeight / 2.0f);
FSlateApplication::Get().OnDebugSafeZoneChanged.Broadcast(SafeZoneRatio, true);
if (UUserWidget* DefaultWidget = GetDefaultWidget())
{
// If we using custom or desired design time sizes and the user picks a screen size, they must
// want to also change the visualization to be custom on screen or desired on screen, doesn't
// make sense to change it otherwise as it would have no effect.
if (DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::Custom)
{
DefaultWidget->DesignSizeMode = EDesignPreviewSizeMode::CustomOnScreen;
}
else if (DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::Desired)
{
DefaultWidget->DesignSizeMode = EDesignPreviewSizeMode::DesiredOnScreen;
}
MarkDesignModifed(/*bRequiresRecompile*/ false);
}
BroadcastDesignerChanged();
ResolutionTextFade.Play(this->AsShared());
}
bool SDesignerView::HandleIsCommonResolutionSelected(const FPlayScreenResolution InResolution) const
{
// If we're using a custom design time size, none of the other resolutions should appear selected, even if they match.
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
if ( DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::Custom || DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::Desired )
{
return false;
}
}
int32 TestHeight = InResolution.Height;
int32 TestWidth = InResolution.Width;
// Swap the width and height to test if the preview is currently flipped
if ((InResolution.bCanSwapAspectRatio) && (PreviewWidth > PreviewHeight))
{
TestHeight = InResolution.Width;
TestWidth = InResolution.Height;
}
// Compare to the size in the settings
const bool bSizeMatches = (((TestWidth == WidthReadFromSettings) && (TestHeight == HeightReadFromSettings))
|| (((bCanPreviewSwapAspectRatio && (PreviewWidth > PreviewHeight)) || InResolution.bCanSwapAspectRatio) && (TestHeight == WidthReadFromSettings) && (TestWidth == HeightReadFromSettings))); // flipped to landscape
if (!PreviewOverrideName.IsEmpty() || !InResolution.ProfileName.IsEmpty())
{
// Would have the same r.MobileContentScaleFactor and original size
return InResolution.ProfileName.Equals(PreviewOverrideName) && bSizeMatches;
}
return bSizeMatches;
}
FUIAction SDesignerView::GetResolutionMenuAction( const FPlayScreenResolution& ScreenResolution )
{
FExecuteAction OnResolutionSelected = FExecuteAction::CreateRaw( this, &SDesignerView::HandleOnCommonResolutionSelected, ScreenResolution );
FIsActionChecked OnIsResolutionSelected = FIsActionChecked::CreateRaw( this, &SDesignerView::HandleIsCommonResolutionSelected, ScreenResolution );
return FUIAction( OnResolutionSelected, FCanExecuteAction(), OnIsResolutionSelected );
}
TOptional<int32> SDesignerView::GetCustomResolutionWidth() const
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
return FMath::TruncToInt32(DefaultWidget->DesignTimeSize.X);
}
return 1;
}
TOptional<int32> SDesignerView::GetCustomResolutionHeight() const
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
return FMath::TruncToInt32(DefaultWidget->DesignTimeSize.Y);
}
return 1;
}
void SDesignerView::OnCustomResolutionWidthChanged(int32 InValue)
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
DefaultWidget->DesignTimeSize.X = InValue;
MarkDesignModifed(/*bRequiresRecompile*/ false);
}
}
void SDesignerView::OnCustomResolutionHeightChanged(int32 InValue)
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
DefaultWidget->DesignTimeSize.Y = InValue;
MarkDesignModifed(/*bRequiresRecompile*/ false);
}
}
EVisibility SDesignerView::GetCustomResolutionEntryVisibility() const
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
const bool bCustomSizing =
DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::Custom ||
DefaultWidget->DesignSizeMode == EDesignPreviewSizeMode::CustomOnScreen;
return bCustomSizing ? EVisibility::Visible : EVisibility::Collapsed;
}
return EVisibility::Collapsed;
}
UUserWidget* SDesignerView::GetDefaultWidget() const
{
TSharedPtr<FWidgetBlueprintEditor> BPEd = BlueprintEditor.Pin();
if (UUserWidget* Default = BPEd->GetWidgetBlueprintObj()->GeneratedClass->GetDefaultObject<UUserWidget>())
{
return Default;
}
return nullptr;
}
TSharedRef<SWidget> SDesignerView::GetResolutionsMenu()
{
UCommonResolutionMenuContext* CommonResolutionMenuContext = NewObject<UCommonResolutionMenuContext>();
CommonResolutionMenuContext->GetUIActionFromLevelPlaySettings = UCommonResolutionMenuContext::FGetUIActionFromLevelPlaySettings::CreateRaw(this, &SDesignerView::GetResolutionMenuAction);
return UToolMenus::Get()->GenerateWidget(ULevelEditorPlaySettings::GetCommonResolutionsMenuName(), CommonResolutionMenuContext);
}
TSharedRef<SWidget> SDesignerView::GetScreenSizingFillMenu()
{
FMenuBuilder MenuBuilder(true, nullptr);
CreateScreenFillEntry(MenuBuilder, EDesignPreviewSizeMode::FillScreen);
CreateScreenFillEntry(MenuBuilder, EDesignPreviewSizeMode::Custom);
CreateScreenFillEntry(MenuBuilder, EDesignPreviewSizeMode::CustomOnScreen);
CreateScreenFillEntry(MenuBuilder, EDesignPreviewSizeMode::Desired);
CreateScreenFillEntry(MenuBuilder, EDesignPreviewSizeMode::DesiredOnScreen);
return MenuBuilder.MakeWidget();
}
void SDesignerView::CreateScreenFillEntry(FMenuBuilder& MenuBuilder, EDesignPreviewSizeMode SizeMode)
{
const static UEnum* PreviewSizeEnum = StaticEnum<EDesignPreviewSizeMode>();
// Add desired size option
FUIAction DesiredSizeAction(
FExecuteAction::CreateRaw(this, &SDesignerView::OnScreenFillRuleSelected, SizeMode),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &SDesignerView::GetIsScreenFillRuleSelected, SizeMode));
FText EntryText = PreviewSizeEnum->GetDisplayNameTextByValue((int64)SizeMode);
MenuBuilder.AddMenuEntry(EntryText, FText::GetEmpty(), FSlateIcon(), DesiredSizeAction, NAME_None, EUserInterfaceActionType::Check);
}
FText SDesignerView::GetScreenSizingFillText() const
{
const static UEnum* PreviewSizeEnum = StaticEnum<EDesignPreviewSizeMode>();
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
return PreviewSizeEnum->GetDisplayNameTextByValue((int64)DefaultWidget->DesignSizeMode);
}
return FText::GetEmpty();
}
bool SDesignerView::GetIsScreenFillRuleSelected(EDesignPreviewSizeMode SizeMode) const
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
return DefaultWidget->DesignSizeMode == SizeMode;
}
return false;
}
void SDesignerView::OnScreenFillRuleSelected(EDesignPreviewSizeMode SizeMode)
{
if ( UUserWidget* DefaultWidget = GetDefaultWidget() )
{
DefaultWidget->DesignSizeMode = SizeMode;
MarkDesignModifed(/*bRequiresRecompile*/ false);
}
}
const FSlateBrush* SDesignerView::GetAspectRatioSwitchImage() const
{
if (PreviewHeight > PreviewWidth)
{
return FAppStyle::Get().GetBrush("UMGEditor.OrientPortrait");
}
return FAppStyle::Get().GetBrush("UMGEditor.OrientLandscape");
}
bool SDesignerView::GetAspectRatioSwitchEnabled() const
{
return bCanPreviewSwapAspectRatio;
}
bool SDesignerView::GetFlipDeviceEnabled() const
{
return PreviewWidth > PreviewHeight && !PreviewOverrideName.IsEmpty();
}
void SDesignerView::BeginTransaction(const FText& SessionName)
{
if ( ScopedTransaction == nullptr )
{
ScopedTransaction = new FScopedTransaction(SessionName);
for ( const FWidgetReference& SelectedWidget : GetSelectedWidgets() )
{
if ( SelectedWidget.IsValid() )
{
SelectedWidget.GetPreview()->Modify();
SelectedWidget.GetTemplate()->Modify();
}
}
}
}
bool SDesignerView::InTransaction() const
{
return ScopedTransaction != nullptr;
}
void SDesignerView::EndTransaction(bool bCancel)
{
if ( ScopedTransaction != nullptr )
{
if ( bCancel )
{
ScopedTransaction->Cancel();
}
delete ScopedTransaction;
ScopedTransaction = nullptr;
}
}
FReply SDesignerView::HandleZoomToFitClicked()
{
ZoomToFit(/*bInstantZoom*/ false);
return FReply::Handled();
}
FReply SDesignerView::HandleSwapAspectRatioClicked()
{
bSafeZoneFlipped = false;
// If in default orientation (portrait for table/phone, landscape for monitor/laptop/TV)
if ((WidthReadFromSettings < HeightReadFromSettings) == (PreviewWidth < PreviewHeight))
{
PreviewHeight = WidthReadFromSettings;
PreviewWidth = HeightReadFromSettings;
}
else
{
PreviewHeight = HeightReadFromSettings;
PreviewWidth = WidthReadFromSettings;
}
ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault<ULevelEditorPlaySettings>();
const UDeviceProfile* DeviceProfile = UDeviceProfileManager::Get().FindProfile(PreviewOverrideName, false);
// Rescale the swapped sizes that are from the initial settings load
if (DeviceProfile)
{
float TempScaleFactor = 1.0f;
PlayInSettings->RescaleForMobilePreview(DeviceProfile, PreviewWidth, PreviewHeight, TempScaleFactor);
}
bPreviewIsPortrait = (PreviewHeight > PreviewWidth);
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewWidth"), PreviewWidth, GEditorPerProjectIni);
GConfig->SetInt(*ConfigSectionName, TEXT("PreviewHeight"), PreviewHeight, GEditorPerProjectIni);
GConfig->SetString(*ConfigSectionName, TEXT("PreviewAspectRatio"), *PreviewAspectRatio, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bIsInPortraitMode"), bPreviewIsPortrait, GEditorPerProjectIni);
GConfig->SetString(*ConfigSectionName, TEXT("ProfileName"), *PreviewOverrideName, GEditorPerProjectIni);
GConfig->SetFloat(*ConfigSectionName, TEXT("ScaleFactor"), ScaleFactor, GEditorPerProjectIni);
GConfig->SetBool(*ConfigSectionName, TEXT("bCanPreviewSwapAspectRatio"), bCanPreviewSwapAspectRatio, GEditorPerProjectIni);
if (!PreviewOverrideName.IsEmpty())
{
DesignerSafeZoneOverride = PlayInSettings->CalculateCustomUnsafeZones(CustomSafeZoneStarts, CustomSafeZoneDimensions, PreviewOverrideName, FVector2D(PreviewWidth, PreviewHeight));
}
else
{
FSlateApplication::Get().ResetCustomSafeZone();
FSlateApplication::Get().GetSafeZoneSize(DesignerSafeZoneOverride, FVector2D(PreviewWidth, PreviewHeight));
}
FMargin SafeZoneRatio = DesignerSafeZoneOverride;
SafeZoneRatio.Left /= (PreviewWidth / 2.0f);
SafeZoneRatio.Right /= (PreviewWidth / 2.0f);
SafeZoneRatio.Bottom /= (PreviewHeight / 2.0f);
SafeZoneRatio.Top /= (PreviewHeight / 2.0f);
FSlateApplication::Get().OnDebugSafeZoneChanged.Broadcast(SafeZoneRatio, true);
if (UUserWidget* DefaultWidget = GetDefaultWidget())
{
MarkDesignModifed(/*bRequiresRecompile*/ false);
}
BroadcastDesignerChanged();
ResolutionTextFade.Play(this->AsShared());
return FReply::Handled();
}
FReply SDesignerView::HandleFlipSafeZonesClicked()
{
if (!PreviewOverrideName.IsEmpty())
{
ULevelEditorPlaySettings* PlaySettings = GetMutableDefault<ULevelEditorPlaySettings>();
if (!bSafeZoneFlipped)
{
DesignerSafeZoneOverride = PlaySettings->FlipCustomUnsafeZones(CustomSafeZoneStarts, CustomSafeZoneDimensions, PreviewOverrideName, FVector2D(PreviewWidth, PreviewHeight));
bSafeZoneFlipped = true;
}
else
{
DesignerSafeZoneOverride = PlaySettings->CalculateCustomUnsafeZones(CustomSafeZoneStarts, CustomSafeZoneDimensions, PreviewOverrideName, FVector2D(PreviewWidth, PreviewHeight));
bSafeZoneFlipped = false;
}
}
FMargin SafeZoneRatio = DesignerSafeZoneOverride;
SafeZoneRatio.Left /= (PreviewWidth / 2.0f);
SafeZoneRatio.Right /= (PreviewWidth / 2.0f);
SafeZoneRatio.Bottom /= (PreviewHeight / 2.0f);
SafeZoneRatio.Top /= (PreviewHeight / 2.0f);
FSlateApplication::Get().OnDebugSafeZoneChanged.Broadcast(SafeZoneRatio, true);
if (UUserWidget* DefaultWidget = GetDefaultWidget())
{
MarkDesignModifed(/*bRequiresRecompile*/ false);
}
BroadcastDesignerChanged();
ResolutionTextFade.Play(this->AsShared());
return FReply::Handled();
}
EVisibility SDesignerView::GetRulerVisibility() const
{
return EVisibility::Visible;
}
bool SDesignerView::IsSelectableInSequencer(UWidget* const InWidget) const
{
const TSharedPtr<FWidgetBlueprintEditor> WidgetBlueprintEditor = BlueprintEditor.Pin();
if (!WidgetBlueprintEditor.IsValid()
|| WidgetBlueprintEditor->GetCurrentAnimation() == UWidgetAnimation::GetNullAnimation())
{
return true;
}
const TSharedPtr<ISequencer>& Sequencer = WidgetBlueprintEditor->GetSequencer();
if (!Sequencer.IsValid() || !Sequencer->IsViewportSelectionLimited())
{
return true;
}
return Sequencer->IsObjectSelectableInViewport(InWidget);
}
void SDesignerView::OnSelectedAnimationChanged()
{
const TSharedPtr<FWidgetBlueprintEditor> WidgetBlueprintEditor = BlueprintEditor.Pin();
if (!WidgetBlueprintEditor.IsValid())
{
return;
}
const TSharedPtr<ISequencer>& ActiveSequencer = WidgetBlueprintEditor->GetSequencer();
if (!ActiveSequencer.IsValid())
{
return;
}
if (ActiveSequencer->IsViewportSelectionLimited())
{
DeselectNonSequencerWidgets();
}
}
void SDesignerView::OnSelectionLimitedChanged(const bool bInEnabled)
{
if (bInEnabled)
{
DeselectNonSequencerWidgets();
}
}
void SDesignerView::DeselectNonSequencerWidgets()
{
const TSharedPtr<FWidgetBlueprintEditor> WidgetBlueprintEditor = BlueprintEditor.Pin();
if (!WidgetBlueprintEditor.IsValid())
{
return;
}
const TSharedPtr<ISequencer>& ActiveSequencer = WidgetBlueprintEditor->GetSequencer();
if (!ActiveSequencer.IsValid())
{
return;
}
const TSet<FWidgetReference>& SelectedWidgets = WidgetBlueprintEditor->GetSelectedWidgets();
if (SelectedWidgets.IsEmpty())
{
return;
}
TSet<FWidgetReference> NewSelection;
for (const FWidgetReference& WidgetReference : SelectedWidgets)
{
UWidget* const Widget = WidgetReference.GetPreview();
if (!IsValid(Widget))
{
continue;
}
if (!ActiveSequencer->IsViewportSelectionLimited()
|| WidgetBlueprintEditor->GetCurrentAnimation() == UWidgetAnimation::GetNullAnimation()
|| ActiveSequencer->IsObjectSelectableInViewport(Widget))
{
NewSelection.Add(WidgetReference);
}
}
WidgetBlueprintEditor->SelectWidgets(NewSelection, false);
}
EVisibility SDesignerView::GetSelectionLimitedTextVisibility() const
{
const TSharedPtr<FWidgetBlueprintEditor> WidgetBlueprintEditor = BlueprintEditor.Pin();
if (!WidgetBlueprintEditor.IsValid())
{
return EVisibility::Collapsed;
}
const TSharedPtr<ISequencer>& ActiveSequencer = WidgetBlueprintEditor->GetSequencer();
if (!ActiveSequencer.IsValid() || !ActiveSequencer->IsViewportSelectionLimited())
{
return EVisibility::Collapsed;
}
return (WidgetBlueprintEditor->GetCurrentAnimation() == UWidgetAnimation::GetNullAnimation())
? EVisibility::Collapsed : EVisibility::SelfHitTestInvisible;
}
#undef LOCTEXT_NAMESPACE