Setting up a Button to start a Drag and Drop Operation

Hello,
I was trying to set up a button to start a Drag and Drop operation, instead of the classic MouseButtonDown.

This is the idea that I was trying to replicate.
BtnMove_Idea

At the moment, my slot has a MenuAnchor to display those buttons.

And when I click on the slot, the menu will open. After that, when I click on the Move button, it will “say to the slot” that it must start a DragDrop Operation, and the menu will be closed.
Prototype_img

My problem here is to start the DragDrop Operation. I already tried different approaches without luck. I think that maybe I’m creating a new operation, but it’s not being returned to nowhere, something that happens on “NativeOnDragDetected”.

NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent,
	UDragDropOperation*& OutOperation)

Below, there is my latest attempt:

I inherit my class from UUserWidget and TSharedFromThis<SObjectWidget>.

After the click on the button, it will say to the slot to run this function:
.cpp

void USlotLayout::BeginDragDrop(ULocalPlayer* LocalPlayer, UDragDropOperation* Operation, int32 PointerIndex) {
	if (LocalPlayer /* && Operation*/ )
	{
		TSharedPtr<const FSlateUser> SlateUser = LocalPlayer->GetSlateUser();
		FVector2D ScreenCursorPos = SlateUser->GetCursorPosition();
		
		FVector2D ScreenDraggedPosition = SlateUser->GetCursorPosition(); 

		const float DPIScale = UWidgetLayoutLibrary::GetViewportScale(LocalPlayer);

		TSharedRef<FUMGDragDropOp> DragDropOp = 
			FUMGDragDropOp::New(Operation, PointerIndex, ScreenCursorPos, ScreenDraggedPosition, DPIScale, SharedThis(this));

		LocalPlayer->GetSlateOperations().BeginDragDrop(DragDropOp);
	}
}

And getting an error on ShareThis(this)

If you have any idea about what’s going on, or if you have another way to do it, I appreciate your help. I’m not sure if I gave enough information, but if you need something more to help me to fix this, just say it.
Thanks!

Hi David, sorry to necro but did you ever figure this out?

Hello MittzyZea.
Unfortunately, I stopped the work that I was doing in this project, and never found a solution for this.

I can try to fix it and return with a solution, but I don’t promise anything.

Have you tried using CommonUI?

I solved this out by using OnMouseButtonDown() to create a DragDropOperation (it was needed a function that returns a FReply).

Nowadays, it must be a cleaner solution.
Let’s not forget that this project is 2+ years old, and it’s kinda outdated.

Hi, Thanks for the response! I didn’t see it for a few days, sorry.

I ended up solving this issue myself by creating a custom button class that supports drag and drop, like you did. Here’s the code for anyone who may find it useful:

(Note: This will not work if you just copy paste it in to your project. It depends on CommonUI and a custom library of mine, but is easily adaptable! Also remember, I’m a noob. There’s probably some bad stuff in here.)

Header File
#pragma once

#include "CoreMinimal.h"
#include "InputCoreTypes.h"
#include "UI/STUserWidget.h"
#include "STUWButton.generated.h"

// ButtonEvent and CursorEvent are similar, but CursorEvent passes the mouses position.
DECLARE_MULTICAST_DELEGATE(FST_ButtonEvent);
DECLARE_MULTICAST_DELEGATE_OneParam(FST_CursorEvent, FVector2D); // FVector2D = Mouse Position


/**
 * A button which supports clicking and dragging.
 */
UCLASS()
class MYPROJECT_API USTUWButton : public USTUserWidget
{
  GENERATED_BODY()

protected:

  virtual void NativePreConstruct() override;

  virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
  virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
  virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
  virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
  virtual FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
  virtual void NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation) override;

public:

  UPROPERTY(meta = (BindWidget)) TObjectPtr<class UCommonBorder> MainButtonBody;
  UPROPERTY(EditAnywhere, meta = (BindWidgetOptional)) TObjectPtr<class UCommonRichTextBlock> MainButtonLabel;
  UPROPERTY(EditAnywhere, meta = (BindWidgetOptional)) TObjectPtr<class UCommonLazyImage> MainButtonImage;
  UPROPERTY(EditAnywhere, meta = (BindWidgetOptional)) TObjectPtr<class UCommonRichTextBlock> SecondaryButtonLabel;
  
  UPROPERTY() TObjectPtr<USTUserWidget> OwningWidget; // The widget that this button is on. Not necessary to set, but you can use this to use GetNewDragDropOperation() on the parent instead of subclassing the button. 

  UPROPERTY() bool bIsPrimaryPressed;
  UPROPERTY() bool bIsSecondaryPressed;
  
  FST_ButtonEvent OnButtonPressedPrimary; // Usually called on Left Mouse Button. This is NOT a click event.
  FST_CursorEvent OnButtonGrabbed; // The same as OnButtonPressedPrimary, but allows you to pass the cursors location.
  FST_ButtonEvent OnButtonPressedSecondary; // Usually called on Right Mouse Button. This is NOT a click event.
  FST_ButtonEvent OnButtonReleased; 
  FST_ButtonEvent OnButtonClickedPrimary; // Usually called on Left Click.
  FST_ButtonEvent OnButtonClickedSecondary; // Usually called on Right Click.
  FST_ButtonEvent OnButtonHovered;
  FST_ButtonEvent OnButtonUnhovered;
  FST_CursorEvent OnButtonDragDetected;
  FST_CursorEvent OnButtonMouseMove;
  
  UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Style, meta = (ExposeOnSpawn = true))
  TSubclassOf<class UCommonButtonStyle> Style; // The buttons style asset // TODO: Make custom UCommonButtonStyle equivalent.
  
  UPROPERTY(EditAnywhere, DisplayName = "Enabled") bool bIsButtonEnabled = true; // Is this button active?
  UPROPERTY(EditAnywhere, DisplayName = "Draggable") bool bIsButtonDraggable = false; // Should this button look for drag-drop events?

  // Not sure how good of an idea this is
  UPROPERTY(EditAnywhere, DisplayName = "Primary Input") FKey PrimaryInputButton = EKeys::LeftMouseButton; // The input that activates the buttons primary function
  UPROPERTY(EditAnywhere, DisplayName = "Secondary Input") FKey SecondaryInputButton = EKeys::RightMouseButton; // The input that activates the buttons secondary function
  
};

C++ File
#include "UI/General/Button/STUWButton.h"

#include "CommonBorder.h"
#include "CommonButtonBase.h"
#include "UI/STUserInterfaceStatics.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
#include "Kismet/KismetInputLibrary.h"

void USTUWButton::NativePreConstruct()
{
  if(MainButtonBody)
  {
    MainButtonBody->SetBrush(Style.GetDefaultObject()->NormalBase);
  }
}

FReply USTUWButton::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
  GEngine->AddOnScreenDebugMessage(-1, 2, FColor::Red, "Mouse Down Detected");
  if(bIsButtonEnabled)
  {
    if(InMouseEvent.GetEffectingButton() == PrimaryInputButton)
    {
      bIsPrimaryPressed = true;
      OnButtonPressedPrimary.Broadcast();
      OnButtonGrabbed.Broadcast(USTUserInterfaceStatics::ScreenSpacePosToViewportSpacePos(this, UKismetInputLibrary::PointerEvent_GetScreenSpacePosition(InMouseEvent)));
      if(bIsButtonDraggable)
      {
        FEventReply EventReply = UWidgetBlueprintLibrary::DetectDragIfPressed(InMouseEvent, this, PrimaryInputButton);
        return UWidgetBlueprintLibrary::CaptureMouse(EventReply, this).NativeReply;
      }
      else{return FReply::Handled();}
    }

    else if(InMouseEvent.GetEffectingButton() == SecondaryInputButton)
    {
      bIsSecondaryPressed = true;
      OnButtonPressedSecondary.Broadcast();
      return FReply::Handled();
    }
  }
  return FReply::Unhandled();

}

FReply USTUWButton::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
  GEngine->AddOnScreenDebugMessage(-1, 2, FColor::Red, "Mouse Up Detected");
  OnButtonReleased.Broadcast();
  OnButtonClickedPrimary.Broadcast(); // NOTE: This is a BAD way of detecting clicks. Ive just not learned the proper way myself yet. I do NOT recommend detecting clicks like this!

  if(InMouseEvent.GetEffectingButton() == PrimaryInputButton)
  {
    bIsPrimaryPressed = false;
    if(HasMouseCapture())
    {
      FEventReply EventReply = FEventReply(true);
      return UWidgetBlueprintLibrary::ReleaseMouseCapture(EventReply).NativeReply;
    }
  }
  if(InMouseEvent.GetEffectingButton() == SecondaryInputButton)
  {
    bIsSecondaryPressed = false;
  }
  return FReply::Handled();
}

void USTUWButton::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
  
}
void USTUWButton::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
{
  
}

FReply USTUWButton::NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
  Super::NativeOnMouseMove(InGeometry, InMouseEvent);
  OnButtonMouseMove.Broadcast(USTUserInterfaceStatics::ScreenSpacePosToViewportSpacePos(this, UKismetInputLibrary::PointerEvent_GetScreenSpacePosition(InMouseEvent)));
  return FReply::Handled();
}


void USTUWButton::NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation)
{
  GEngine->AddOnScreenDebugMessage(-1, 2, FColor::Red, "Drag Detected");
  Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);
  OnButtonDragDetected.Broadcast(USTUserInterfaceStatics::ScreenSpacePosToViewportSpacePos(this, UKismetInputLibrary::PointerEvent_GetScreenSpacePosition(InMouseEvent)));
  
  if(OwningWidget)
  {
    OutOperation = OwningWidget->GetNewDragDropOperation();
  }
}