Drag and drop animates for 0.15 seconds from topleft to pivot/offset.

When a UMG drag-and-drop operation (UDragDropOperation) is commenced in UUserWidget::NativeOnDragDetected a UUserWidget is placed into the DefaultDragVisual which is the visual representation of what is being dragged, and a Pivot and Offset is set to specify the position of this drag visual widget relative to the cursor.

The trouble is that regardless of this pivot and offset the DefaultDragVisual widget is displayed with the cursor at its top left corner, and then animated over 0.15 seconds to the correct Pivot and Offset relative to the cursor.

This bizarre animation is caused by FUMGDragDropOp::OnDragged which lerps for the first 0.15 seconds from the cursor being at the top left of the widget to the specified pivot and offset position:

void FUMGDragDropOp::OnDragged( const class FDragDropEvent& DragDropEvent )
{
	if ( DragOperation && DragDropEvent.GetPointerIndex() == PointerIndex)
	{
		DragOperation->Dragged(DragDropEvent);

		FVector2D CachedDesiredSize = DecoratorWidget->GetDesiredSize();
	
		FVector2D Position = DragDropEvent.GetScreenSpacePosition();
		Position += CachedDesiredSize * DragOperation->Offset;
	
		switch ( DragOperation->Pivot )
		{
		case EDragPivot::MouseDown:
			Position += MouseDownOffset;
			break;
	
		case EDragPivot::TopLeft:
			// Position is already Top Left.
			break;
		case EDragPivot::TopCenter:
			Position -= CachedDesiredSize * FVector2D(0.5f, 0);
			break;
		case EDragPivot::TopRight:
			Position -= CachedDesiredSize * FVector2D(1, 0);
			break;
	
		case EDragPivot::CenterLeft:
			Position -= CachedDesiredSize * FVector2D(0, 0.5f);
			break;
		case EDragPivot::CenterCenter:
			Position -= CachedDesiredSize * FVector2D(0.5f, 0.5f);
			break;
		case EDragPivot::CenterRight:
			Position -= CachedDesiredSize * FVector2D(1.0f, 0.5f);
			break;
	
		case EDragPivot::BottomLeft:
			Position -= CachedDesiredSize * FVector2D(0, 1);
			break;
		case EDragPivot::BottomCenter:
			Position -= CachedDesiredSize * FVector2D(0.5f, 1);
			break;
		case EDragPivot::BottomRight:
			Position -= CachedDesiredSize * FVector2D(1, 1);
			break;
		}
	
		const double AnimationTime = 0.150;
	
		double DeltaTime = FSlateApplicationBase::Get().GetCurrentTime() - StartTime;
	
		if ( DeltaTime < AnimationTime )
		{
			float T = DeltaTime / AnimationTime;
			FVector2D LerpPosition = ( Position - StartingScreenPos ) * T;
			
			DecoratorPosition = StartingScreenPos + LerpPosition;
		}
		else
		{
			DecoratorPosition = Position;
		}
	}
}

I don’t see the point of this animation? It looks terrible. Why doesn’t it just display it in the correct pivot and offset to begin with? Is there anyway to turn off this behaviour?

(My next attempt is hack around this by creating a widget with a canvas panel that has the pivot and offset built into it.)

It’s a virtual function so in your derived class simply override the function and implement it without the animation.

That doesn’t work. Notice that UDragDropOperation, FUMGDragDropOp and FDragDropOperation are three different classes. UDragDropOperation (part of UMG) derives from UObject. FUMGDragDropOp (part of UMG) derives from FDragDropOperation (part of Slate).

A new instance of a subclass of UDragDropOperation is assigned to the OutOperation out parameter of UUserWidget::NativeOnDragDetected, the system then internally wraps that instance in a FUMGDragDropOp. There is no opportunity to provide a subclass ofFUMGDragDropOp and so no way to override FUMGDragDropOp::OnDragged function.

The only reason that its virtual is because FUMGDragDropOp::OnDragged is itself overridding the Slate FDragDropOperation::OnDragged function to implement UMG drag-and-drop layer on top of Slate.

You can see in the first line of the function where FUMGDragDropOp::OnDragged calls DragOperation->Dragged(DragDropEvent) which is a call to the overridable UDragDropOperation::Dragged in the wrapped UDragDropOperation instance, allowing you to add additional code to be running during the drag and drop operation by overriding UDragDropOperation::Dragged, but then FUMGDragDropOp::OnDragged goes on to call the position updating code (and animation) unconditionally.

1 Like

Thanks a lot for the information. I hadn’t really looked at this yet just implemented dragging and thought this ridiculous behaviour would be easy to correct.
I have now changed it in the engine source and don’t see any reason to put it back there unless perhaps you can come up with a less intrusive solution.

For the record I managed to work around it by wrapping the drag visual widget in a canvas panel and then using EDragPivot::MouseDown and a (0,0) offset in the drag operation, applying the desired offset by adjusting the canvas panel slot settings rather than in the drag operation. I think this works because the initial position (StartingScreenPos) is and the calculated position (Position) are the same with those settings (MousDown and 0,0), so the animation lerp is between identical points, and hence nullified.

4 Likes

Has there ever been a proper fix for this? This still seems to be an issue in 5.3. An object dragged on the right hand side of the screen has the drag visual created in the top left corner of the screen and then lerp over to the mouse cursor looking awful.

Wow… You are incredibly smart. :sob:

// Widget calling OnDragDetected event
void UMyWidget::NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation)
{
	Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);
	UCustomDragDropOperation* operation = Cast<UCustomDragDropOperation>(UWidgetBlueprintLibrary::CreateDragDropOperation(UCustomDragDropOperation::StaticClass()));
	
	// Create Drag Widget
	UDragWidget* dragWidget = CreateWidget<UDragWidget>(this, DragWidgetClass);

	UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(dragWidget->DragingImage->Slot);
	slot->SetPosition( InGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition()) );

	operation->DefaultDragVisual = dragWidget;
	operation->Pivot = EDragPivot::MouseDown;

	OutOperation = operation;
}
// Widget creating above
UCLASS()
class PROJECT_E_API UDragWidget : public UBaseWidget
{
public:
	UPROPERTY(Meta = (BindWidget)) class UImage* DragingImage;
};

Canvas Panel > Draging Image (Anchors : Top Left, Alignment : 0.5 0.5)

I’ve been looking for a solution to this too, but not much luck in disabling this “feature” entirely. :sweat_smile:

The best workaround I’ve found is to simply create an animation that fades the drag drop visual in, and have it play on event construct for whatever your drag visual is. There is a slight delay but its workable in my case and the fade in animation actually looks kinda nice.

In my testing you can get away with having your visual start fading in at 0.10 seconds on the timeline and you still won’t see it flying in from the right so its pretty quick.

I struggled with this for a while, the only suitable fix I found was to call SetRenderTransform directly on the new widget i was creating in the other widget’s NativeOnDragDetected, and using a FVector2D::ZeroVector as the Offset in the output UDragDropOperation. Anything else either resulted in it dragging from the other widget’s top left or animating.